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

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)

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 last example is usually called currying or partial function application. But there is technically nothing partial about it. It is just so that f is a function that takes x and returns another function (which in turn than takes y). Functions that return functions were first described by Gottlob Frege back in the 19th century.

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.

The net effect is that when you see a $ you can mentally replace it with parentheses that enclose the rest of the expression.

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

When looking at function composition it is best to read it right-to-left since first buz x is evaluated, then baz, then bar, and finally foo - in the exact same order as when writing the nested parentheses.

In fact, it is very similar to how pipes work on unix systems (only the direction is reversed).

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:

The final overview of all four styles
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:

  1. f is the function reverse

  2. f is doing reverse

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.


Frege Language Reference

Declaration of the $ function
where line numbers might change

results matching ""

    No results matching ""