LEVIATHAN v962456e · 962456eee1

Expressions

Method references

Naming a function, method or labeled constructor without calling it yields an ordinary function value.

since 0.1.0-alpha.1linuxwindowswasm

Description

A callable reached with :: or . that is used as a value, not immediately called, is a first-class function value. It can be stored in a variable, passed as an argument, or kept in an array of function values.

var f = NS::fn;                  // a namespace function: its own signature
var g = User::FromName;          // a labeled constructor: (string) => User
b.handler = Controller::Login;   // an instance method, unbound: the receiver becomes the first parameter
var save = editor.save;          // a bound method: the receiver is captured
menu.onKey(this.onKeyDown);      // `this` may be captured directly

There are two kinds of reference:

  • An unbound reference names a method through its type (Counter::get2). The receiver is not part of the reference, so it becomes the first parameter of the function: Counter::get2, declared as int get2(int k), has the type (Counter, int) => int.
  • A bound reference names a method through an instance (c.bump). It captures the receiver, and the receiver does not appear in the function type: c.bump has the type () => void. It is equivalent to the lambda () => c.bump().

The type of a reference is an ordinary function type, (A, B) => R, so it fits any field, parameter or variable of that type and can be stored in containers such as Array<(int) => int>.

Unbound, bound and constructor references

interface IAnimal {
    string speak();
}

class Animal : IAnimal {
    string speak() => "...";
}

class Dog : Animal {
    string speak() => "Woof";
}

class Counter {
    int n = 0;
    void bump() { n = n + 1; }
    int add(int k) => n + k;
}

class User {
    string name;
    new FromName(string s) { name = s; }
}

namespace Util {
    int twice(int n) => n * 2;
}

void main() {
    var twice = Util::twice;
    console.writeln(twice(21));

    (Counter, int) => int adder = Counter::add;
    Counter c = Counter();
    c.bump();
    console.writeln(adder(c, 10));

    var make = User::FromName;
    console.writeln(make("Zed").name);

    var bump = c.bump;
    bump();
    bump();
    console.writeln(c.n);

    (IAnimal) => string viaInterface = IAnimal::speak;
    (Animal) => string viaClass = Animal::speak;
    console.writeln(viaInterface(Dog()));
    console.writeln(viaClass(Dog()));

    Array<(int) => int> fs = [Util::twice, (n) => n + 1];
    console.writeln(fs.at(0)(5));
}
main();
42
11
Zed
3
Woof
Woof
10

Rules

  • Overloads are resolved by the target function type. When the referenced name is overloaded, the compiler picks the overload that matches the declared type of the variable, field or parameter that receives the reference. A reference to an overloaded name with no target type (var h = Counter::label;) is a compile error: write the target function type.
  • Missing members are a compile error at the reference.
  • Generic callables cannot be referenced without type arguments: M::identity, where identity<R> is generic, is a compile error. Supply the types with ::<...> as in identity::<int>, which is the marker for a reference to a generic function.
  • Dispatch follows the runtime object. A reference behaves like the lambda (C c, ...) => c.m(...), so both an interface reference (IAnimal::speak) and a class reference (Animal::speak) run the override of the object that is passed in, as the example shows with a Dog. See lang.method-dispatch.
  • A bound reference needs a simple receiver. The receiver must be a local variable, a parameter, or this. Something like o.inner.save, this.field.method or make().method is a compile error that tells you to bind the receiver to a local first. This keeps the capture a one-time snapshot instead of silently re-evaluating an expression on every call.
  • A bound reference captures the object, not the variable. Later changes to the object are visible through the reference; assigning a different object to the original variable does not retarget the reference.

A bound reference captures the object it was taken from

class Counter {
    int n = 0;
    void bump() { n = n + 1; }
    int get() => n;
}

void main() {
    Counter a = Counter();
    var bumpA = a.bump;
    var getA = a.get;
    bumpA();
    console.writeln(getA());
    a = Counter();
    bumpA();
    console.writeln(a.n);
    console.writeln(getA());
}
main();
1
0
2

Examples

class Counter {
    string label(int k) => "int";
    string label(string s) => "string";
}
namespace M { R identity<R>(R x) => x; }
class Inner { void save() { } }
class Outer { Inner inner = Inner(); }

void main() {
    var a = Counter::label;
    // error: ambiguous method reference 'Counter::label'; annotate the target function type
    var b = M::identity;
    // error: cannot reference generic function 'identity' in value position
    Outer o = Outer();
    var c = o.inner.save;
    // error: bound method reference receiver must be a bare local, parameter, or 'this'
}
not run — shows a compile error

See also

  • Array — An ordered sequence of values of one type, Array<T>, with value semantics.