Currying and Partial Application
Currying is the technique of converting a function that takes multiple arguments into a sequence of functions that each takes a single argument (wikipedia). Similarly, partial application takes a function and creates a new function by fixing one or several of its arguments.
Curried form in Function definition
Languages like Haskell use the curried form to define functions with multiple arguments. Applied to Fuzion, the idea would be to declare a function like add
add(x,y,z i32) i32 is x+y+z
as a chain of functions with single arguments
add(x i32)(y i32)(z i32) i32 is x+y+z
or, using syntax closer to Haskell (but mixing Haskell's declaration and definition),
add : x i32 -> y i32 -> z i32 -> i32 is x+y+z
The difference between a multi-argument declaration and a declaration using the curried form is just syntactic. Here is my attempt in collecting pros and cons:
style
aspect
|
multi-argument | curried |
---|---|---|
example | add(x,y,z i32) i32 is x+y+z |
or
|
pro |
|
|
cons |
|
|
The current syntax in Fuzion is as follows
element | syntax |
---|---|
feature declaration | add(x,y,z i32) i32 is ... |
function type |
(i32, i32, i32) -> i32
|
feature call |
a := add x y z
or a := add(x, y, z)
|
lambda |
x,y,z -> x+y+z
or (x, y, z) -> x+y+z
|
Partial Application
Using Lambdas
Lambdas permit partial application of features, e.g.,
add(x,y,z i32) i32 is x+y+z addwh i32 -> i32 := d -> add w h d
Using Partial Argument Lists
This syntax can be simplified when only the first arguments are fixed:
addwh i32 -> i32 := add w h
Partial argument lists might make type inference easier than using lambdas such that it might be sufficient to write
addwh := add w h
Using operators
Turning an infix operator into a function with one argument could be achieved
by allowing its use as an prefix or postfix operator as follows: The lambdas
assigned to f
and g
in this code
f i32 -> i32 := x -> x - 3 g i32 -> i32 := x -> 3 - x
Could be replaced using the infix operator as prefix- or postfix-operator to
the partially applied argument 3
:
f i32 -> i32 := - 3 g i32 -> i32 := 3 -
Alternatively, we could work with placeholders _
like Scala does
f i32 -> i32 := _ - 3 g i32 -> i32 := 3 - _
Automatic lambdas
When missing parameters automatically turn a call into a lambda using the
partially applied call, it only makes sense to automatically create lambdas for
fully applied calls as well. Take the following feature log
that
lazily evaluates its argument msg
:
log(msg () -> string) is if logLevel >= level say "LOG: {msg()}"
This would usually be called using a lambda:
log ()->"passing lambda"
While a version with an argument to the lambda:
logLocalized(msg (l language) -> string) is if logLevel >= level say "LOG: {msg default_language}"
could use a lambda
logLocalized (l -> logtext.translate l)
or partial application:
logLocalized logtext.translate
It would hence be consistent to automatically create the lambda in the first case as well:
log "passing lambda implicitly"
In summary, any expression could be turned into a lambda if the expression is a partially applied call and the arguments expected for the target function type together with the partial actual arguments form a proper actual call or if the expression is not a partially applied call and the target function type expects no arguments.
One effect that this would have is that for the caller, there is no longer any syntactic difference between passing a value directly and passing a lazily evaluated closure.