<| associativity

595 views
Skip to first unread message

Mark Hamburg

unread,
Feb 25, 2016, 12:56:24 PM2/25/16
to Elm Discuss
Does someone have an example of where having this be right associative is helpful? If I define:

f : Int -> String -> ()

Then I would think that I could write:

f <| 3 <| "Hello"

but that fails because it tries to pass "Hello" to the non-function 3.

I'm assuming that the right associativity was chosen for a reason, but it's not coming to me what that reason would have been.

Thanks.
Mark

Bernd Meyer

unread,
Feb 25, 2016, 12:59:50 PM2/25/16
to Elm Discuss

Max Goldstein

unread,
Feb 25, 2016, 1:05:09 PM2/25/16
to Elm Discuss
In your case, just right f 3 "Hello". No operator needed.

As Bernd writes, <| is typically used to add a small preface to a large chunk of code. Here's a tiny little function, and it receives the result of this big chunk of code here.

|> is actually more common, and used for pipelining. circle 20 |> filled blue |> moveX 50, that sort of thing. Very easy to pick up once you've seen a few examples.

Mark Hamburg

unread,
Feb 25, 2016, 1:40:13 PM2/25/16
to Elm Discuss
I simplified my case down to minimal code to show the issue. I know that I don't need the operator there.

And the one example supplied only uses the <| operator once and hence its associativity doesn't matter.

So, I'm still left asking: Is there a use case where it's better that <| be right associative rather than left associative? Is it that we want to be able to write something like show <| ... whole mess of stuff .. and be able to put an essentially arbitrary collection of stuff on the right which might itself include a use of <|?

Mark

Mark Hamburg

unread,
Feb 25, 2016, 1:45:00 PM2/25/16
to Elm Discuss
This comes up because I've at times wanted to write

function
    <| complex expression for argument 1
    <| complex expression for argument 2
    <| complex expression for argument 3

Now, one could argue that using a let expression and naming the expression results would be a good idea and I certainly wouldn't dispute that once the expressions get complex enough, but there's a threshold where it feels appealing to write it inline given the availability of the <| operator and the fact that it works for the single argument case.

Mark

Joey Eremondi

unread,
Feb 25, 2016, 1:46:57 PM2/25/16
to elm-d...@googlegroups.com

No, the entire point of the operator is to be left associative. If you really need complex expressions, just use parentheses. Elm format will take care of the rest for you.

--
You received this message because you are subscribed to the Google Groups "Elm Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email to elm-discuss...@googlegroups.com.
For more options, visit https://groups.google.com/d/optout.

Mark Hamburg

unread,
Feb 25, 2016, 1:49:25 PM2/25/16
to elm-d...@googlegroups.com
But the operator isn't left associative. It's right associative. And my question is "Why?"

Mark
You received this message because you are subscribed to a topic in the Google Groups "Elm Discuss" group.
To unsubscribe from this topic, visit https://groups.google.com/d/topic/elm-discuss/-PLj_eamKVQ/unsubscribe.
To unsubscribe from this group and all its topics, send an email to elm-discuss...@googlegroups.com.

Joey Eremondi

unread,
Feb 25, 2016, 2:07:48 PM2/25/16
to elm-d...@googlegroups.com

Whoops, I mixed up left and right! The whole point of the operator is that (f <| x <| y ) == (f (x y) ).

The goal is to avoid complex nested expressions, so that Elm doesn't end up looking like Lisp.

Consider where you are adding 4 elements to a dict:

insert wk w <| insert xk x <| insert yk y <| insert zk z myDict.

Without a right-associative operator, this becomes:

insert wk w (insert xk x  (insert yk y  (insert zk z myDict)))

In my mind, this is harder to read, and you get a huge collection of parentheses at the end.

In your case, you can always take this:

function
    <| complex expression for argument 1
    <| complex expression for argument 2
    <| complex expression for argument 3

and write it as this:

function
    (complex expression for argument 1)
    (complex expression for argument 2)
    (complex expression for argument 3)

There are brackets, but they're not nested, so there isn't a readability issue.

The idea is that function application is left-associative by default, just by placing two things next to each other. We define <| to be right-associative for the cases when the default isn't what you need.

Like Max said, we can do things in a pseudo-OO style if we use the reverse operator, |>. Then, my example can be written as:

myDict |> insert zk z |> insert yk y |> insert xk k |> insert wk w


This is probably the clearest, and it corresponds nicely to what you'd see in an object-oriented language:

 myDict.insert(zk z).insert(yk y).insert(xk x).insert(wk w)

Mark Hamburg

unread,
Feb 25, 2016, 2:43:47 PM2/25/16
to elm-d...@googlegroups.com
Thank you. That was exactly what I was looking for.

Søren Debois

unread,
Feb 28, 2016, 3:01:55 PM2/28/16
to Elm Discuss
I agree with the OP that <| would be more convenient if it were left associative. I don't agree that "adding brackets" isn't a readability issue; clutter is clutter.

In F#, <| is left-associative; you use it to avoid brackets or naming things with let-bindings when you have to do multi-line computation to arrive at function arguments. I find this very compelling.

The "myDict" example in Joey's post does not adequately explain why <| is right-associative in Elm since, as the post says, that is example is more naturally written with |>.

So what is a good example?

Joey Eremondi

unread,
Feb 28, 2016, 3:23:04 PM2/28/16
to elm-d...@googlegroups.com
clutter is clutter

Do you have code examples which show this, even after running through Elm-format? Change never happens in Elm without concrete examples from real code.

<| in Elm was originally $, modeled after Haskell's $, which is right associative. So we like F#'s naming was nice, but historically there's more of a connection to Haskell's version.

It looks like they've had the opposite discussion in F#, and the response here is probably the same: if you really want it, it can be implemented as an operator in a library. But changing it now would break tons of existing code, and there is a use for both operators. But if you have a large chain of applications, using |> or parenthesis is probably better.

Søren Debois

unread,
Feb 29, 2016, 2:43:24 AM2/29/16
to Elm Discuss
I think no brackets and '<|' would be nicer than just brackets for multi-line function applications: The pipe operator clearly marks where one argument stops and the next starts, and I don't have the awkard terminating brackets. But that might be a matter of preference.

However, I'm still hoping to see an example of how to properly use the right-associative pipe-operator <| in Elm, since I apparently want to use it non-idomatically.  What is that example?

Max Goldstein

unread,
Feb 29, 2016, 10:21:48 AM2/29/16
to Elm Discuss
I'm not sure multiple <| are ever idiomatic, making associativity moot. I find that multiple operators always read better forward with |>. Do this, then do this... the other way is an artifact of mathematical notation, where f(g(x)) is read inside-out, right to left. Ick.

Again, <| can be used once to indicate a small prefix to a lot of code.

Jason Merrill

unread,
Feb 29, 2016, 10:26:59 AM2/29/16
to Elm Discuss
On Monday, February 29, 2016 at 2:43:24 AM UTC-5, Søren Debois wrote:
However, I'm still hoping to see an example of how to properly use the right-associative pipe-operator <| in Elm, since I apparently want to use it non-idomatically.  What is that example?

It's "just" function composition. f <| g x in Elm is like (f∘g)(x) in "math notation" is like f(g(x)) in either.

Here are 3 examples of computing the same function (a 5th order Chebyshev polynomail, but that doesn't matter)

Lots of braces:

> t5 x = cos(5*acos(x))
<function> : Float -> Float
> t5 0.2
0.8451200000000004 : Float

No braces, using <|

> t5 x = cos <| (*) 5 <| acos x
<function> : Float -> Float
> t5 0.2
0.8451200000000004 : Float

Note that forcing left association actually breaks this example, so from my POV, making <| left associative would break it's only real use:

> t5 x = (cos <| (*) 5) <| acos x
-- TYPE MISMATCH --------------------------------------------- repl-temp-000.elm

The right argument of (<|) is causing a type mismatch.

4         cos <| (*) 5)
                 
^^^^^
(<|) is expecting the right argument to be a:

   
Float

But the right argument is:

    number
-> number

Hint: I always figure out the type of the left argument first and if it is
acceptable on its own
, I assume it is "correct" in subsequent checks. So the
problem may actually be
in how the left and right arguments interact.

No braces, pipeline style, using |>

> t5 x = x |> acos |> (*) 5 |> cos
<function> : Float -> Float
> t5 0.2
0.8451200000000004 : Float

As you can see, the <| version is really just exactly the reverse of the |> pipeline version.

For math expressions, I think the heavily parenthesized version is actually the clearest because it's the most similar to traditional math notation and therefore the most familiar.

For things like a map -> filter -> foldl chain, I think the pipelined |> version is the clearest because you can read it directly from left to right.

So when is <| the clearest? Not that often, really, and I think you won't see it show up a whole lot in most people's Elm code. If you really want to think of the composition of two functions (probably not mathematical functions) as a single operation, then you might sometimes prefer f <| g x over f(g(x)) because there's no nesting, but it really doesn't seem like a big deal to me.

Max Goldstein

unread,
Feb 29, 2016, 1:49:41 PM2/29/16
to Elm Discuss
Good points, Jason.

Here's an example, a binary tree.

type Tree a = Leaf | Node (Tree a ) a (Tree a)
  
map : (a -> b) -> Tree a -> Tree b
map f tree =
  case tree of
    Leaf -> Leaf
    Node left val right ->
      ??


How should we write the last line? Pick one:

Node (map f left) (f val) (map f right)
Node <| map f left <| f val <| map f right

No wait, only the first one compiles. It's also shorter (objectively) and looks a lot better (subjectively). So, assuming I am correct to think the latter would work with associativity switched, it's not exactly code to encourage. As has been pointed out, the first line has lots of parens, but no nested parens, so it's okay.

Tim Stewart

unread,
Feb 29, 2016, 11:30:59 PM2/29/16
to Elm Discuss
I always think of <| as the "APL operator". APL is right-associative language, you basically have to read an APL expression from right to left.
Reply all
Reply to author
Forward
0 new messages