Beginner question: Does the $ serve 2 purposes?

I understand that the $ sybmol is aright associative and in some cases it can replace the need to write multiple parenthesis .

For example:
instead of this
hello = add 5 (6 + 7) I can write hello = add 5 $ 6 + 7

Great. that way 6 and 7 evaluates first before getting applied to the add function.
Okay So here is where my confusion lays.

head . sort $ "julie"

In my head this looks like it takes the opposite effect of the first. As in don’t evaluate sort with julie. instead call sort with “julie” after the composition has been applied.

Link to the book .
http://learnyouahaskell.com/higher-order-functions#function-application

There’s actually two important concepts when dealing with operators: associativity and precedence. Let’s look at some arithmetic operators first since they’re probably more familiar.
Minus (-) is left-associative. If I say 10 - 5 - 3, we know that that’s (10 - 5) - 3, rather than 10 - (5 - 3). BUT that doesn’t mean we can uniformly work left-to-right in all cases, because some operators have a higher precedence than minus. In this case, let’s add multiplication. If I say 10 - 5 * 3, we know that’s actually 10 - (5 * 3) even though subtraction is left-associative, just because multiplication has a higher precedence.

Now you can see from this table that $ is the lowest precedence possible (0), while . is the highest precedence possible (9), and precedence rules apply before associativity.

1 Like

Okay, I think I am starting to understand it more.
This made me re-arrange my thinking on the example
add 5 $ 6 + 7
It’s not that it makes the parenthesis invisible. I originally thought it was just syntactic sugar. But instead
add 5 gets called first as it takes the most precedence. then 6 + 7 is ran and evaluates (Because it takes the lowest precedence. ). The result is then passed to add 5 as the second argument.

  • I just want to make a quick note here. There is not built in function in Haskell(That I currently know of *) called add. I was just using that as an example as to what the function would do. In this case the function add would take 2 arguments and add them together — For Future readers who come across this post.
1 Like

And idiomatically speaking, you can select between

a $ b $ c $ d

And

a . b . c $ d

As you see fit. The latter has the benefit that it emphasizes that the first three terms are functions and the last term is the value to which they are being applied, and also encourages the reader to think in terms of what functions you are composing rather than step-by-step applying each function to d.

You can also perform the straightforward abstraction

let f = a . b . c in f d 

Which you cannot do if you’re using all dollar signs.

Perspective #1: Algebra skills are very helpful in Haskell. A formula is worth a thousand pictures. (So a million words.)

  head . sort $ "julie"        explicit parenthesizing
= (head . sort) $ "julie"      definition of ($)
= (head . sort) "julie"        definition of (.)
= head (sort "julie")

Perspective #2: The simplistic model “$ gets rids of parentheses” would be a great story for ELI5. But see xkcd: Like I'm Five

You are not 5, you are ready for a more precise model: f $ x is f x but with a different precedence, so that you can have a larger expression instead of f, and a larger expression instead of x.

Perspective #3: Beginners should simply avoid $. Use parentheses. sin (x+y) and (head . sort) xs are very clear.

2 Likes

I think I understand it now. I made a diagram :nerd_face: to clear the understanding. Let me know if this is accurate.

I think this block is worded a little funny and I want to emphasize a couple things just to make sure you have it correct
image

You mention “function application” there for sq $, but the function sq is not being applied yet. The $ function is being applied, but follows operator rules for precedence and associativity, not “normal” function application rules (like you’d have for (f x)). In fact in the example sq $ sqrt $ 7 + 9 there is no “normal” function application at all, just the application of 3 operators, so any commentary about function application being highest precedence and left associative doesn’t apply to this example. Since $ is right-associative, (sq $) doesn’t happen first, it happens last.

I’d also like to clarify that since $ is an operator, sq $ is passing sq into the $ function, not passing $ into the sq function.

Now if we look at an example like sq $ add 5 $ 3 * 2, there’s some “normal” function application in there with the add function. So add 5 has the highest precedence and happens first to get sq $ (add 5) $ 3 * 2, then the * since that’s higher precedence than $ - sq $ (add 5) $ (3 * 2) - then the right $ since $ is right-associative - sq $ ((add 5) (3 * 2)) - and then finally the left $ - (sq ((add 5) (3 * 2))).

I’m a little confused about what you mean by some of the boxes in the diagram so I cannot say whether or not your understanding is accurate.

What I can say is that the box in red has a false presupposition. I don’t know if your sentence means “why doesn’t sq $ throw an error” or “why doesn’t sq $ sqrt $ 7 + 9 throw an error”, but either way, both of those will throw errors. Specifically, both of them are functions and functions do not have a Show instance so if you ask GHCi to print them (which is what typing an expression into it and pressing enter does), it will give you an error saying that there isn’t an instance of Show for the type of the expression. If sq takes two arguments then sq $ is a function waiting for two arguments, so you can’t show it in GHCi. Similarly, sq $ sqrt $ 7 + 9 is a function which is still waiting for one argument and then will apply sq to 4 and that number, and again that cannot be shown in the REPL. If sq took one argument then sq $ sqrt $ 7 + 9 would return a value, specifically whatever value sq gives when given 4. So, I’m not really sure what you meant in the red box.

Edit: what ntwilson said above is probably the actual issue that is causing confusion.

sq $ sqrt $ 7 + 9

is not sq being applied to two expressions, it is $ being applied to two expressions, one of which is sq and the other of which is sqrt $ 7 + 9, in the same way that 7 + 9 is not 7 being applied to two expressions, it’s (+) being applied to 7 and 9.

A lot of these issues can be checked on your own by typing them into GHCi and seeing that they do give errors.

1 Like

You make two statements about order of evaluation that should be amended.

The $ operator does not force evaluation of the second argument. That 6 + 7 might never be evaluated. Consider this example:

add a b = a

If we call add 5 $ 6 + 7 then the thunk (unevaluated computation) of 6+7 won’t be evaluated at all. This is because the language is non-strict. Values are not evaluated unless demanded (or as optimized by the compiler because of forseen later demand).

For further exemplification, you instead of having a computation you could have an exception and it won’t get thrown:

Prelude> add a b = a
Prelude> add 5 $ error "oh no!"
5

Or less contrived, it could be advantageous to avoid numeric exceptions:

Prelude> let (x,n) = (481, 0)
Prelude> flatOp a b = if b == 0 then 0 else a
Prelude> flatOp (x `div` n) n
0

This despite the divide by zero exception one would get if we computed 481 `div` 0

1 Like

That last bit helped a lot. Specifically

sq $ (add 5) $ (3 * 2) - then the right $ since $ is right-associative - sq $ ((add 5) (3 * 2)) - and then finally the left $ - (sq ((add 5) (3 * 2))).

You also bring up another point which was

there is no “normal” function application at all, just the application of 3 operators, so any commentary about function application being highest precedence and left associative doesn’t apply to this example.

Thank you!

I never thought about associations all to much in Javascript or Python. It’s nice to really dig in deep. I will admit, I probably spent way to much time on this.