Chat about elm/parser operators |. and |=

I just listened to Elm Radio Episode 6 - elm/parser. It was great! I’ve never used elm/parser myself (yet!) and would love to hear more from people who have.

elm/parser exposes two operators: |. and |=. Here’s an example from the docs:

point : Parser Point
point =
    succeed Point
        |. symbol "("
        |. spaces
        |= float
        |. spaces
        |. symbol ","
        |. spaces
        |= float
        |. spaces
        |. symbol ")"

It was interesting hearing how Dillon and Jeroen were “pronouncing” these operators! For example, around 14:40:

  • keep” … “– and discard?”
  • “I was thinking there was a nicer four-letter word for both…”
  • “Pipe-equals – like, vertical bar-equals operator – and there’s vertical bar-dot”
  • “You do pipe-equals, or pipe-dot”
  • “Pipe-equals is going to capture the result of that parser” … “and if you do pipe-dot [explanations about the fact that you expect certain characters, but are not keeping any data from them]”

This sparked two questions:

  1. How do you pronounce them?

  2. What if they weren’t actually operators, and instead were called like you pronounce them? For example:

point : Parser Point
point =
    succeed Point
        |> skip (symbol "(")
        |> skip spaces
        |> keep float
        |> skip spaces
        |> skip (symbol ",")
        |> skip spaces
        |> keep float
        |> skip spaces
        |> skip (symbol ")")

Do you think the |. and |= operators make all the difference to making your parser readable, or are they more of a cute thing? (I’ve never used elm/parser myself, and as a bystander the skip/keep example looks just as readable.)

3 Likes

One thing that’s important in such a pipeline is the ability to have the “keep” lines stand out to someone who is scanning the code. |= and |. do this nicely. That’s a property to keep in mind if designing a textual API.

3 Likes

That is a good point! I’d love to see an example of this in some package that uses elm/parser.

I use elm/parser to parse Elm types for the package website, so you can take a look at how the operators look in that context:

I wrote this parser to meet the goal of parsing types fast enough that it’s not a problem on the package website, so I’m not sure how well it works as a learning resource. It seems alright looking over it, but I’d give more consideration if it was meant for learning!

3 Likes

Thanks!

I tried replacing all |. p with |> skip p and all |= p with |> capture p, just to see what it looks like. It’s available here in case anyone’s interested: https://github.com/elm/project-metadata-utils/compare/master...lydell:textual

An interesting observation I made is that the “parser pipelines” are pretty short in that package, compared to the example in the docs! Is that common? It feels like longer pipelines benefit more form the operators than short ones.

3 Likes

Custom operators are something I always liked in languages and missed them when they were not there. I though elm having no custom operators was going to be annoying for me, and it was. Not having them though, has helped me develop a richer vocabulary for describing the problems I’m trying to solve and have a better concept of what API I’m trying to make. It also removes a sense of ambiguity of what an operator means because it is literally spelled out for you. I think it is an interesting choice to introduce a new custom operator for specifically the parser package. It seems that as an initial roll out using a pipeline with |> skip p and |> capture p for example would have been sufficient if reducing custom operators was a goal with the language. I think having operators that are inflexible in this way do not provide a large benefit other than making this package usage more succinct. These seem to lead to conflicting goals. If there are other reasons why this decision was made I would love hear. I’m also aware how verbose some parser definitions can get so I can see the drive for shorter methods as well.

3 Likes

In my opinion, custom operators are one of those things that “seem like a good idea at the time”. I remember when I first started learning Haskell, I was really excited that I could make custom operators and I used them all over the place, but they really hurt the readability of the code. Even more annoying than that, searching for functions that are defined as custom operators is just painful. I can’t think of any circumstance where they offer any real value, including |. and |= in elm/parser.

2 Likes

I think custom infix operators are useful in some situations.

When I used to work with C# I would sometimes look at Java and think “It must be annoying to write vector math in Java, you can’t overload infix operators*. You have to write v1.add(v2) instead of just v1 + v2!”

Then I started using Elm and I found that I didn’t mind the lack of custom operators. So what changed?

The conclusion I’ve come to is that custom operators are important in languages that lack the ability to pipe values.If you do have |> then the downsides tend to outweigh the advantages.

Suppose we have code that deals with 2d vectors and we want to take a vector, scale it, translate it, and lastly rotate it.

  • With |> but no custom infix operators: v |> scale 0.5 |> translate offset |> rotate 90 Easy to read from left to right
  • With no |> with custom infix operators: rotate 90 (v |*| 0.5 |+| offset) Easy-ish to read since we are familiar with math notation
  • With no |> and no custom infix operators: rotate 90 (translate offset (scale 0.5 v)) Annoying to read because we have to keep track of parenthesis nesting

*C# lets you overload existing operators and here we are talking about custom operators. I’m going to mix them up because I think they have similar advantages and disadvantages.

5 Likes

This may show one motivation for the custom operators: avoiding parentheses. Is that worth it? Arguably not though maybe it pushes for one more standard operator to handle this pretty common case with pipelines — i.e. something that would let one write something like:

|> skip: symbol “(“ 

For some value of colon.

Any real evaluation would need to look at a lot more usage to see whether the drop in parenthesis usage is worth the cost of another operator.

1 Like

Wouldn’t that be written someVector.rotate(90).translate(offset).scale(0.5) ?

Good point, I had forgotten about chaining functions. Kind of ruins my idea then :slightly_smiling_face:

This topic was automatically closed 10 days after the last reply. New replies are no longer allowed.