Audience
I assume familiarity with the Haskell programming language or some other language that supports calling a function with less arguments than the function's signature requires. This is called "partial application of a function".
Consider the following function-call expression in Haskell:
(alpha beta gamma)
What can be deduced?
alpha
is a function (either imported or in a variable).In most languages (that disallow implicit partial application) you could additionally deduce:
alpha
takes exactly 2 argumentsalpha
.Neither of those two properties are necessarily true in Haskell.
In this case, the expression behaves as you’d expect in any other language. In particular:
alpha
takes exactly 2 argumentsalpha
.In this case, the expression is a partial application of alpha
, and would be perhaps better read as:
(\ ... -> alpha beta gamma ...)
Or, in a more C-like notation:
function (...) { return alpha(beta, gamma, ...); }
Thus:
alpha
takes exactly 2+K arguments, for some unknown K.Let’s say alpha
takes 1 argument. This would be intuitively read as:
((alpha beta) gamma)
After this reformulation you need to recursively examine the new expression.
In this example, we can deduce:
(alpha beta)
returns an anonymous function of (at least) 1 argument.gamma
, and the original expression’s return type matches the return type of the anonymous function.gamma
, and yet another anonymous function (that takes K parameters) is returned as the result of the original expression.What a mess. If alpha
does not in fact take two arguments then I have to exert non-trivial effort to derive the type of the expression - or even what the expression semantics are.
There are two ways I can see to simplify these weird cases:
Then we would see syntax like:
-- alpha takes 2 arguments -- return type matches that of alpha (alpha beta gamma) -- alpha takes 2+K arguments; -- return type is a partially applied K-argument function (alpha beta gamma ...) -- alpha takes 1 argument and returns a 1-argument function; -- expression's return type matches that of the 1-argument function ((alpha beta) gamma) -- alpha takes 1 argument and returns a (1+K)-argument function; -- expression's return type is a partially applied K-argument function ((alpha beta) gamma ...)
Notice that each conceptually different case now has a unique syntactic representation - it’s no longer just (alpha beta gamma)
for all cases.
For example parentheses could be used instead of a space to signify function calls.
Using a grouping operator syntactically prohibits relying on the left-associativity of space for partial function application, since a grouping operator doesn’t have associativity.
If you combined this suggestion with the explicit syntax extension above, you would get syntax like:
-- alpha takes 2 arguments -- return type matches that of alpha alpha(beta, gamma) -- alpha takes 2+K arguments; -- return type is a partially applied K-argument function alpha(beta, gamma, ...) -- alpha takes 1 argument and returns a 1-argument function; -- expression's return type matches that of the 1-argument function alpha(beta)(gamma) -- alpha takes 1 argument and returns a (1+K)-argument function; -- expression's return type is a partially applied K-argument function alpha(beta)(gamma, ...)
I originally got bitten by this syntactic ambiguity when trying to decipher the meaning of:
(flip (/) 20)
I was not previously familar with flip
and so I assumed that it took two arguments, a function (namely (/)
) and a non-function (namely 20
). And that it probably returned a function, since its surrounding context expected a function.
In fact flip
takes only one argument and thus is more clearly written as:
((flip (/)) 20)
And inlined further to be the lower-order:
(\x -> x / 20)
And, if desired, further rewritten to be the more-compact:
(/ 20)
This last form uses partial application to fill in the first argument of /
, which is obvious since /
is a well-known built-in infix operator that requires an unspecified left argument.