Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

What's the priority for function composition in Haskell?

Tags:

haskell

I saw this code on my textbook:

double :: (Num a) => a -> a
double x = x * 2

map (double.double) [1,2,3,4]

What I don't get is that if functional composition operation have the highest priority, why use parentheses to include double.double? If I remove those parentheses, I get error message. So what's exactly is functional composition's priority?

like image 696
user3489985 Avatar asked Dec 10 '22 05:12

user3489985


2 Answers

All of the built-in operators' respective precedences and fixities can be found in the Haskell Report section 4.4.2. In particular, . and !! have precedence 9, which is the highest among operators. However, function application is not an operator. Function application is specifically designed to have precedence higher than any operator, so

map (double.double) [1,2,3,4]

This is applying the function double . double to each element of the list [1, 2, 3, 4]

map double.double [1,2,3,4]

This is attempting to compose the functions map double and double [1, 2, 3, 4], which is unlikely to be successful (though it is not technically impossible).

like image 64
Silvio Mayolo Avatar answered Dec 28 '22 02:12

Silvio Mayolo


Precedence (and associativity) are ways of resolving the ambiguity between multiple infix operators in an expression. If there are two operators next to an operand, precedence (and associativity) tells you which of them takes the operand as an argument and which of them takes the other applied-operator expression as an argument. For example, in the expression 1 + 2 * 3, the 2 is next to both + and *. The higher precedence of * means that * gets the 2 as its left argument, while + takes the whole 2 * 3 sub-expression as its right argument.

However that's not the case in map double.double [1, 2, 3, 4]. There's only one operator, with two operands on either side, so there's no question for precedence to answer for us. The two operands are map double and double [1, 2, 3, 4]; operands are sequences of one or more terms, not only the immediate left and right terms. Where there's more than one term the sequence is parsed as simple chained function application (i.e. a b c d is ((a b) c) d).

Another way to think of it is that there is an "adjacency operator" which has higher precedence than can be assigned to anything else, and is invisibly present between any two non-operator terms (but not anywhere else). In this way of thinking map double.double [1, 2, 3, 4] is really something like map @ double . double @ [1, 2, 3, 4] (where I've written this "adjacency operator" as @). Since @ has higher precedence than ., this is (map @ double) . (double @ [1, 2, 3, 4]).1

Whichever way you choose to interpret it, there is a simple consequence. It is simply impossible for any applied operator expression to be passed as an argument in non-operator function application unless there are parentheses around the operator application. If there is an operator in an expression outside any parentheses, then the outermost layer of the expression is always going to be an operator application.


1 This adjacency operator explanation seems to be pretty common. I personally think it is a poor explanation for how to parse expressions, since you need to partially parse an expression to know where to insert the adjacency operators.

It's often called the "whitespace operator", which is even more confusing since not every piece of whitespace represents this operator, and you don't always need whitespace for it to be there. e.g. length"four" + 1

like image 45
Ben Avatar answered Dec 28 '22 01:12

Ben