docs/types/dynamic-dispatch.md
Enso is a language that supports pervasive dynamic dispatch. This is a big boon for usability, as users can write very flexible code that still plays nicely with the GUI.
The current implementation of Enso supports single dispatch (dispatch purely on
the type of self) when calling function. When calling (binary) operators Enso
may perform more complicated dispatch when searching for the right operator
implementation to invoke.
Another page related to dispatch exists.
In order to determine which of the potential dispatch candidates is the correct one to select, the compiler needs to have a notion of specificity, which is effectively an algorithm for determining which candidate is more specific than another.
[!WARNING] Static compiler selects nothing. The right method to invoke is selected in the runtime.
- Always prefer a member function for both
x.f yandf y xnotations.- Only member functions, current module's functions, and imported functions are considered to be in scope. Local variable
fcould not be used in thex.f ysyntax.- Selecting the matching function:
- Look up the member function. If it exists, select it.
- If not, find all functions with the matching name in the current module and all directly imported modules. These functions are the candidates.
- Eliminate any candidate
Xfor which there is another candidateYwhosethisargument type is strictly more specific. That is,Ythis type is a substitution ofXthis type but not vice versa.- If not all of the remaining candidates have the same this type, the search fails.
- Eliminate any candidate
Xfor which there is another candidateYwhich type signature is strictly more specific. That is,Ytype signature is a substitution ofXtype signature.- If exactly one candidate remains, select it. Otherwise, the search fails.
The runtime system of Enso identifies the type of a value in obj.method_name
invocation. It checks the table of virtual methods for given type and finds
proper implementation of method_name to invoke. Should there be no method of
given name in the value's type (or its supertypes like Any) to invoke, a
No_Such_Method panic is raised.
There is a special dispatch for broken values & warnings.
Multiple dispatch is currently used for binary operators.
Multiple dispatch is also used on from conversions, because in expression
T.from x the function to use is based on both T and x.
[!WARNING] Supporting general multiple dispatch is unlikely
Supporting it for general functions remains an open question as to whether we want to support proper multiple dispatch in Enso. Multiple dispatch refers to the dynamic dispatch target being determined based not only on the type of the
thisargument, but the types of the other arguments to the function.To do multiple dispatch properly, it is very important to get a rigorous specification of the specificity algorithm. It must account for:
- The typeset subsumption relationship.
- The ordering of arguments.
- How to handle defaulted and lazy arguments.
- Constraints in types. This means that for two candidates
fandg, being dispatched on a typetwith constraintc, the more specific candidate is the one that explicitly matches the constraints. An example follows:rubytype HasName name : String greet : t -> Nothing in IO greet _ = print "I have no name!" greet : (t : HasName) -> Nothing in IO greet t = print 'Hi, my name is `t.name`!' type Person Pers (name : String) main = p1 = Person.Pers "Joe" greet p1 # Hi, my name is Joe! greet 7 # I have no nameHere, because
Personconforms to theHasNameinterface, the secondgreetimplementation is chosen because the constraints make it more specific.
TODO: Remove this section?
AnySpecial attention must be paid to Any and its methods and extension methods.
Any is a super type of all objects in Enso. As such the methods available on
Any are also available on every object - including special objects like those
representing type and holding its static methods (discussed at
types page as well).
There is a to_text instance method defined on Any - what does it mean when
one calls Integer.to_text? Should it by treated as:
Any.to_text Integer # yields Integer text
or should be a static reference to Integer.to_text method without providing
the argument? In case of regular types like Integer the following code:
main = Integer.to_text
is considered as invocation of instance method to_text on object Integer
and yields Integer text.
The situation is different when a module static method together with Any
extension method is defined:
# following function makes sure `simplify` can be called on any object
Any.simplify self = "Not a Test module, but"+self.to_text
# following "overrides" the method on Test module
simplify = "Test module"
With such a setup following code invokes Any.simplify extension method
main = "Hi".simplify
and yields Not a Test module, but Hi text. On contrary the following code
yields Test module value:
main = Test.simplify
When invoking a method on module object its module static methods take
precedence over instance methods defined on Any. Thus a module serves
primarily as a container for module (static) methods.
Terminology:
name=expression.self named argument
provided.
self=expression does not have to be specified as the first
argument, but is is a good convention to do so.self named argument provided.My_Type is a type of type, usually written as
My_Type.type.
Meta.type_of Singleton_Type is Singleton_Type.Meta.type_of Normal_Type, which is
Normal_Type.type.My_Module is a type for the module
@Builtin_Type.
My_Type is a type that My_Type "extends", i.e.
My_Type inherits all the methods defined on its parent type.
Any type.Any has no parent type.Any.Float and Integer builtin types have Number parent.Number has Any parent.This section describes the method invocation process, which resolves a
concrete method definition for a concrete call site and evaluates it. For a
method call expression Receiver.symbol, this section focuses only on a single
dispatch based on the Receiver argument. For multiple dispatch, see the
Multiple Dispatch section.
This section is further divided into Instance method invocation and Static method invocation. Note the differences between these types of invocations:
self named argument provided.self argument implicitly.Instance method invocation is any method invocation without self named
argument specified (see terminology). Before a method is invoked, it needs to be
resolved. Method resolution algorithm for the Receiver.symbol expression
first determines the type of the Receiver, and then finds the method
definition in its symbol table:
Receiver:Receiver is type, the result will be eigen type.Receiver is singleton type, the result will be the type itself.Receiver is a value (instance / atom), the result will be the type
of the value.Receiver is a module, the result will be the associated type for
the module.Receiver is a polyglot object, method resolution and invocation will
be handled according to the polyglot interoperability
rules.
polyglot java import ... statement.Java_Class.new ... expression.Receiver, we are just looking for a variable in the
current lexical scope or any parent scopes. See
Lexical scope lookup.symbol in the Receiver's type and all its parent types.No_Such_Method panic and stop.symbol is a method in Receiver's type (or its parent type) symbol
table.self and has
the preapplied value of Receiver. In other words, the method invocation is
equivalent to the method self=Receiver expression.Static method invocation is any method invocation with self named argument
provided (see terminology). Let's consider the following static method
invocation expression: Receiver.symbol self=receiver.
symbol in Receiver (not its type!) and all its parent
types.
Receiver is not a type itself, it has no symbol table, so no
method is found.No_Such_Method panic and stop.Receiver or its parent type.self and has no
default value.receiver expression to self parameter.symbol is defined in the current lexical scope, select it and stop.symbol is defined in the scope, select it and stop.symbol in all transitively imported modules (in DFS?). If
symbol is defined in any of the modules, select it and stop.Name_Not_Found panic and stop.@Builtin_Type
type Any
to_text self = "???"
type My_Type
Cons
method self x = x + 1
obj = My_Type.Cons
Evaluation of obj.method 41:
My_Type (1.3)method is looked up in My_Type symbol table, and found (2.2)method is executed as My_Type.method self=obj x=41 (3.2)Evaluation of My_Type.method obj 41:
My_Type.type (1.1)method in My_Type.type symbol table (2.3)No_Such_Method panic.Evaluation of My_Type.method self=obj 41:
method on My_Type (4.1).My_Type.method is found (4.2).My_Type.method self=obj x=41 (5.2).Evaluation of Any.to_text obj:
Any.type (1.1)Any.type.to_text method (2.3)No_Such_Method panic.Evaluation of Any.to_text self=obj:
to_text on Any (4.1).Any.to_text is found (4.2).Any.to_text self=obj (5.2)."???".Evaluation of My_Type.to_text:
My_Type.type (1.1)to_text is found in Any symbol table (2.2)
Any is parent of My_Type.type.Any.to_text self=My_Type (3.2)"???".