f(x) -- as known from many languages
f x -- Frege style (like linear functions in mathematics)
f $ x -- explicit function application with the $ operator
f(x,y) -- taking a tuple as an argument
f x y -- same as ((f x) y)
Silent Notation
In a language that is built from functions and expressions, one can reasonably expect to see a lot of parentheses. It is no coincidence that LISP is humorously known for its proverbial overload with parentheses.
Frege has a very silent notation, though, and supports a coding style that uses very little parentheses at all.
Lets start with function application.
Function application
Frege uses the blank character - the most silent one in the character set -
to denote function application. Function application has the highest
precedence of all operators. It binds more tightly than all others.
It is also left associative such that f x y
is really
two function applications: first f x
, which returns a function that then takes
y
as an argument.
The dollar sign
When a function application like g x
appears as an argument to f then
it must be written in parentheses like f (g x)
because otherwise, g would
become an argument to f, which is not what we want. This can get unwieldy
when the nesting gets deeper and therefore there is an alternative using
the dollar sign.
foo (bar (baz (buz x))) -- deeply nested style
foo $ bar $ baz $ buz x -- flat $ style
Interestingly, the $ operator is not a language construct. It is an ordinary
function that is declared as an infix operator. The definition of $ is
f $ x = f x
. The trick is that $ has very low operator precedence and therefore
all operators to its right (and analogously to the left as we will see later)
are applied before its result is finally applied to f.
Function composition
Now, even if we love to see dollars, the $ operator is certainly not very silent in its appearance, but
the dot-operator is. It it the operator for function composition:
f(g(x))
can - just like in school math - be written as (f • g) x
.
(foo . bar . baz . buz) x -- function composition with dot
(foo • bar • baz • buz) x -- Frege also allows unicode 2022
We said that function application has the highest precedence - even higher than function composition. Therefore, we need the parentheses above to prevent the binding between buz and x. But, hey, we learned that dollar has lowest precedence, so putting that in between should also work, right? Yes it does.
foo . bar . baz . buz $ x -- this is not used so often
Now, can we shave the x off such that we can drop dollars and parentheses as well?
Yes, but that needs a bit of background.
Functions are lambdas
Let’s assume we have a method inc
that just adds 1 to its argument.
There are two equivalent ways of writing this:
inc x = x + 1 -- usual function definition
inc = \x -> x + 1 -- equivalent lambda style
Point-free style
Now if we want a function f to work like inc, we can use inc in the definition of f and relay the argument.
f x = inc x -- f should be like inc
f = inc -- point-free style, "cancel x"
But we can even cancel down the x such that f looks like an alias for inc. And here is proof that this is a valid transformation:
f = inc -- beta reduction =>
f = \x -> x + 1 -- replace formal lambda parameter with free variable =>
f x = x + 1 -- same as definition of inc
This finally leaves us with four ways of defining our function:
f x = foo (bar (baz (buz x))) -- deeply nested style
f x = foo $ bar $ baz $ buz x -- flat $ style
f x = (foo . bar . baz . buz) x -- function composition
f = foo . bar . baz . buz -- point-free style
Keeping the above equivalence in mind should help you to become confident with these options when writing your own code but even more so when reading other developer’s code.
You will find all four styles in any reasonably sized program.
For example, we have used the point-free style in the previous post, when
we said f = reverse
and I’m sure you guessed the meaning even
though it was not explained. We could have said f x = reverse x
but there
is a subtle distinction - not in what the code does but in what it
communicates:
-
f
is the functionreverse
-
f
is doingreverse
The prototypical adoption cycle for newcomers is: they are first puzzled by the point-free style, then excited, then they overuse it, then they settle at a reasonable dose.
References
Frege Language Reference | |
Declaration of the $ function |
https://github.com/Frege/frege/blob/master/frege/prelude/PreludeBase.fr#L109-109 |