Advanced overloading

Multiple expected types

Due to overloading, an expression can have multiple expected types.
In most cases, this is basically like having no expected type, but in some cases it can matter.
Below, 1 has two expected types nat8 and string. It chooses nat8 since a number literal can't be a string.
With no expected type, the type would have been nat instead.

main void() log foo 1 foo nat8(a nat8) a * 10 foo string(a string) # not used a

Effects of left to right type checking

Since type checking works from left to right, if you do need type annotations, it's best to put them on earlier arguments.
This gives the type checker information earlier.

import keen/bits main void() # 'a' is 'nat8' a = foo 0::nat8, 1 log count-ones ~a # 'b' is 'nat64' ('1::nat8' implicitly converts to 'nat64') b = foo 0, 1::nat8 log count-ones ~b foo[t] t(a t, _ t) a

More examples

main void() log foo::string bar foo nat(a int) # this is not used a.to foo string(a symbol) log "Using foo string(a symbol)" a.to bar int() # this is not used 1 bar symbol() log "Using bar symbol()" "a"

Keen type-checks function calls from left to right (assuming prefix syntax). So:

  • If first sees a call to foo expecting a string (due to the ::string at the call).
    There is only one foo function that returns a string, so the other overload is discarded.
  • Then it checks the argument to foo expecting a symbol, since that is the parameter type of the only surviving foo overload.
  • That argument is a call to bar.
    There is only one bar returning a symbol, so the other overload is discarded.
  • After checking the call to bar, it now has an argument type symbol and finishes checking the call to foo.
    The argument type matches the remaining foo's parameter type, so it's done.

Another example:

main void() log foo bar foo string(a bool) # This is not used "{a}" foo string(a symbol) log "Using foo string(a symbol)" a.to bar int() # this is not used 1 bar symbol() log "Using bar symbol()" "a"
  • If first sees a call to foo.
    There are two foo functions, and it can't discard any based on the number of arguments or return type.
  • Then it checks the argument to foo expecting a bool or symbol, since those are the parameter types of the foo functions.
  • That argument is a call to bar. Only the bar returning symbol matches one of the expected types.
    The bar returning int is discarded as it can't match bool or symbol.
  • After checking the call to bar, it now has an argument type symbol and finishes checking the call to foo.
    Since only one foo declares a symbol parameter, it uses that one.