5
$\begingroup$

Several OO languages allow you to grab unapplied method references.

Some, such as Kotlin or Python, have them accessible from the class itself and just append self onto the front of the argument list.

class IntWrapper(private val x: Int) {
  fun adding(other: IntWrapper) = IntWrapper(x + other.x)
}

val additionFunc: (IntWrapper, IntWrapper) -> IntWrapper = IntWrapper::adding

Excuse the terrible example, I can't think of any practical one off the top of my head.

JavaScript handles this notoriously poorly, due to the weird rules around this:

class NumberWrapper {
  constructor(x) { this.x = x; }
  adding(other) { return new NumberWrapper(this.x + other.x); }
}

const additionFunc = NumberWrapper.prototype.adding;
let fifteen = additionFunc.apply(new NumberWrapper(5), [new NumberWrapper(10)]);
// OR
const addFive = additionFunc.bind(new NumberWrapper(5));
fifteen = addFive(new NumberWrapper(10));

Swift handles this in a way I don't think I've seen before, modelling unapplied references as currying in a way that's not otherwise common in the language.

// Example method call, for comparison
let x = 1.0
_ = x.addingProduct(2.0, 3.0)

// Now, for the unapplied reference:
let fma: (Double) -> (Double, Double) -> Double = Double.addingProduct
// what the heck is that type signature?

Some languages, like C#, don't provide any mechanism for this, and you have to explicitly write out x => x.Foo().

What are the advantages and disadvantages to each approach? What other approaches exist?

$\endgroup$
0

1 Answer 1

2
$\begingroup$

C++ has std::mem_fun() to turn a pointer-to-member-function into a function object:

class IntWrapper {
    int x;
public:
    IntWrapper(int x): x(x) {}
    IntWrapper adding(const IntWrapper& other) {
        return {x + other.x};
    }
};

auto additionFunc = std::mem_fun(&IntWrapper::adding);
auto three = additionFunc(IntWrapper(1), IntWrapper(2));

You can use the pointer-to-member-function itself instead of using std::mem_fun(), but the syntax is rather awful:

auto additionFunc = &IntWrapper::adding;
auto three = (IntWrapper(1).*additionFunc)(IntWrapper(2));

There is std::bind() as well to do currying. But all that results in objects that act like regular functions: you call them with some parameters, and a value comes out. What happens in your Swift example:

let fma: (Double) -> (Double, Double) -> Double = Double.addingProduct
// what the heck is that type signature?

This is not a function that returns a value, but you are declaring a function that takes a Double and returns another function that has the signature (Double, Double) -> Double. So the call fma(1.0) returns a function that takes two parameters and that evaluates Double(1.0).addingProduct(/* those parameters */); fma(1.0)(2.0, 3.0) is then equivalent to Double(1.0).addingProduct(2.0, 3.0).

In C++ you could write:

auto fma(double z) {
    return [z](double x, double y) {
        return std::fma(x, y, z);
    }
}

And call it like fma(1.0)(2.0, 3.0) as well. Swift's syntax doesn't use lambda expressions, it's more like the pointer-to-member function from above, but with the benefit that you can call it the same way as a normal function, whereas C++ needs that special syntax.

I don't think Swift has any benefits for flexibility or performance, but it definitely is shorter to write.

Something I don't see though is a way to make this generic in Swift; you can't write:

let fma<T>: (T) -> (T, T) -> T = T.addingProduct

Whereas in C++ you could write:

auto fma = [](auto z) {
    return [z](auto x, auto y) {
        return std::fma(x, y, z);
    }
};
$\endgroup$

You must log in to answer this question.

Start asking to get answers

Find the answer to your question by asking.

Ask question

Explore related questions

See similar questions with these tags.