In Haskell, given any function with two arguments (curried) we can choose to use it infix position. The syntax for this depends on how we name the function; if we name the function in the form (s)
then s
is the infix form of the function, and otherwise an arbitrary function f
can be converted to infix form by wrapping in backticks, as in `f`
.
In either case, the infix form of the function expects the arguments in a certain order, and the order used in Haskell is:
(s) x y := x s y
I think it would be more natural if the order was the other way around, so:
(s) x y := y s x
I’ll give some examples which I think illustrate why this ordering is more natural.
We’ll mainly look at examples where (s)
is non-commutative, because it is only in this case that there will be a difference between the result of x s y
and y s x
.
My first example is from natural language. In natural language, a connective can be thought of as a function that takes two sentences and returns a new sentence.1 Examples of connectives are ‘and’, ‘or’, ‘because’, ‘when’, ‘if’, ‘but’. The connectives ‘and’ and ‘or’ are of course commutative, but the last four do depend on the order of their arguments. Let’s take ‘because’ as an example. Consider the following sentence in infix form: “I ate because I was hungry”. Which prefix form is this equivalent to – “because I ate, I was hungry” or “because I was hungry, I ate”? It is the latter, which has the order of the component sentences interchanged. This is the case whatever sentence you choose that includes ‘because’ or any of the other connectives (that have a prefix form). It is also not a feature unique to the English language (although I’m not sure whether it applies to all languages2).
My second reason also comes from an analogy with natural language, but this time looking at the structure within a simple sentence. A simple sentence consists of a subject, a verb and an object in a particular order. In English, the order is subject-verb-object (SVO), but almost all of the six possible orderings exist in other languages. Certain orders are significantly more widespread than others though, and the relative frequency can be explained by two principles3; one is that the subject should generally come before the object in a sentence (the Topic-first principle), and the other is that the object is more closely tied to the verb than the subjects is (the Verb-object bonding principle) – The verb and object together form what is called the ‘verb-phrase’. There are only two orders that satisfy both of these principles which are SVO and SOV, and these account for more than 75% of all languages (with SVO being actually slightly less common than SOV4)5. In an analogy between a simple sentence in natural language and an expression in functional programming, it is clear the verb should be analogous to the function (s)
. Now because function application is curried, x
is naturally tied to (s)
because the partial application (s) x
has a meaning by itself as a function, which in my mind naturally corresponds to the concept of a verb-phrase in natural language. This makes me read (s) x y
as VOS, and of course the infix form is natural to read as SVO, and so this is what makes me see (s) x y := y s x
.
Other examples I want to think about are from arithmetic. When I read the expression 2 + 3
, there are two ways I think about it6; either there is a symmetry between 2
and 3
and they are both ‘vectors’ which are being composed together, or there is an asymmetry and 2
is the subject, a point, and +3
is the translation being applied to the point 2
. In the latter interpretation +3
is a function that is natural to think of as the partial application of (+)
to 3
, and therefore the whole thing is equivalent to (+) 3 2
. The advantages of viewing it this way are even more clear in the case of the minus operation, as the minus operation is non-commutative. Again, there are two ways you can interpret the expression 2 - 3
; either you can interpret it as the directed difference between 2
and 3
as points, or you can look at it as the translation -3
being applied to the point 2
. Again it is the latter interpretation that curried application supports, and therefore the curried prefix form should be (-) 3 2
. There are further benefits to this ordering, when it comes to folding over a list (which otherwise can have unnatural results with the wrong ordering because (-)
is non-associative). Consider the expression foldr (-) 0 [1,2,3]
. With (-) x y := x - y
, this expands to (1-(2-(3-0))
which evaluates to 2
, but with (-) x y := y - x
it expands to ((0-1)-2)-3
which evaluates to -6
, which is a lot more natural. (You could argue that this is reversed if you use foldl
instead of foldr
, but that’s because the way foldl
is defined is unnatural, and in fact precisely cancels out the unnaturality in the infix ordering.)
This alternative ordering also ties in nicely with how infix ordering is done in Scala. In Scala, as in Java, everything is a method applied to an object, so expressions look like x.f(y)
. Now x.f(y)
is often implemented as f y x
in Haskell e.g. consider mapping over a list which would be List(1,2,3).map(_+2)
in Scala and is map (+2) [1,2,3]
in Haskell. But also there is clearly an affinity of x.f(y)
to the infix form x f y
. Indeed Scala allows you to use x f y
as syntactic sugar for x.f(y)
. (Incidentally this is a really neat trick, and in particular allows the standard arithmetic notation, x + y
, to exist as first class in the language, unlike in Java where it has to be a primitive). Therefore we see that f x y
in Haskell often corresponds to the infix form y f x
in Scala.
I think ultimately the reason that the ordering feels most natural the other way round, is because of the curried application of functions. This means that (s) x
is read as a function by itself that can then be applied to y
. Infix form is then just like applying this partially applied function in the postfix position instead of the prefix position. The handy little operator (&)
allows you to apply any function as postfix instead of prefix; naturally it is defined simply as flip ($)
and just like how $
has low right associativity, &
has low left associativity. I think using &
is the best way to write an arbitrary function in infix form, as it makes this ‘postfix’ structure explicit, and generalises naturally to functions with more than 2 arguments. As a toy illustration, with this approach we could write 1 + 2 + 3 + 4
as 1 &(+) 2 &(+) 3 &(+) 4
; in the case of +
we perhaps wouldn’t want to do this, but for more custom operations I think the latter is clearer as it makes it explicit how you are supposed to read it.
There is one spanner in the works though, which is (:)
– the function that appends an item to a list. I think it’s natural for (:)
to be defined as it is, taking the item as the first argument and the list as the second argument, but I also want to be able to write x : xs
, which relies on the existing infix ordering. Scala gets round this issue by having any symbol which starts with a :
automatically bind to the left instead of the right, which seems a little arbitrary. After thinking about this problem for a while, I’ve come to the conclusion that really we should have two directed infix operators for this purpose, which I suggest have symbols <::
and ::>
, where (::>)
is equivalent to Haskell’s current (:)
. I came to this idea by analogy with how function application is done in Elm. In Elm, there are two infix symbols used for function application which are |>
and <|
. These correspond respectively to &
and $
in Haskell, but they are so much more natural as the directions are naturally indicated, and the duality of these operations is reflected in the reflective symmetry of the symbols. (Also I like the use of the |
as it looks like the pipe operator in UNIX which serves a very similar purpose). Also in Elm there are similar symbols for composition, which are >>
and <<
(incidentally I think .>
and <.
could be nice alternatives). Function application, composition and list appending are all very similar things; composition is just ‘lifted’ function application and list construction is just like composition in the free monoid, waiting to be realised and evaluated as a composition of functions. (Indeed as a summary of this relationship, we can write f1 . f2 . f3 . f4
as foldr (.) id [f1, f2, f3, f4]
). So to me it makes sense if the list appending operator also makes the direction clear in its infix forms (and ideally also in the syntactic sugar e.g. <[1,2,3]
). In this way we can continue to write x <:: xs
, but we remember the direction of the operator is flipped in the prefix form to (::>) x xs
.
The argument about infix ordering is obviously not a matter of which is more correct, as clearly both orderings encode the same information; it is purely an argument of which is more natural and readable. I think my suggestion that the opposite ordering is more natural is not just a personal preference, or even a preference unique to English speakers or speakers of related languages, but actually an innate grammatical preference that we all share7. It is the relation to grammatical orders that is my strongest reason for why the ordering should be the other way round, as if we can make use of our innate and subconscious grammatical parser when reading programs, we can save our cognitive efforts for higher level things.
- I’ve talked more about this analogy between natural language and functional expressions in ‘Grammar is like a Type System’. ↩
- I’ve asked the question on linguistics stack exchange here, but haven’t had a conclusive answer yet. ↩
- Russell Tomlin argued these principles in his 1986 paper “Basic word order. Functional principles” and they are described here. They are also referred to in Chapter 8 of ‘The Language Instinct’. ↩
- An interesting programming equivalent of the common SOV order in natural languages is Reverse Polish Notation. ↩
- The statistics are from the Wikipedia article for Subject-verb-object. ↩
- I talked about these two interpretations of addition in more detail in my post ‘The Real Numbers’. ↩
- It makes sense to talk about grammatical preferences that we all share because language is an instinct, as argued in ‘The Language Instinct’. ↩
After more thought, the current order feels a lot like the convention for order of composition of functions in mathematical notation. Both standard mathematical notation and Haskell have settled on `f . g` meaning “do f after g”. And actually even though I think it reads more naturally as “f then g” (and I think I have heard of some mathematicians using that against convention), I can see the merits of the standard order as for example if the function `f` is something like `const 4` or `(&&) False`, then reading from left to right you don’t need to evaluate `g` to know the behaviour of `f . g`. Also it’s nice that the order looks the same as when you are applying the function `(f . g) x = f(g(x))`.
Link to Reddit
As an interesting side note, I found by googling that there is an interesting programming equivalent to the common SOV order in natural language called Reverse Polish Notation. It’s nice because you don’t need any brackets.
EDIT: Have added this as a footnote.
Something else that works well with the current order but wouldn’t work well with the opposite order is infinite lists like
[0..]