3

I want to update a record using lens with a value parsed by attoparsec.

fmap (myRecord & _2 . someField .~) double

And it totally doesn't work:

Iddq3.hs:99:48:
    The operator ‘.~’ [infixr 4] of a section
        must have lower precedence than that of the operand,
          namely ‘&’ [infixl 1]
        in the section: ‘myRecord & _2 . someField .~’

What does this error mean? What are infixr and infixl? How can I rewrite the function to correct it?

Christian Conkle
  • 5,932
  • 1
  • 28
  • 52
Michael Fox
  • 3,632
  • 1
  • 17
  • 27
  • 2
    You can, you just can't mix those operators quite like that, try `fmap ((myRecord &) . (_2 . someField .~)) double`. The problem comes from fixity, like the error message says. Alternatively, you can use `fmap (\v -> set (_2 . someField) v myRecord) double`, which I think looks better. – bheklilr Oct 30 '14 at 21:45
  • @bheklilr Thanks. That does look nicer – Michael Fox Oct 30 '14 at 22:02
  • @Christian Conkle, I mean, I did read it. But I don't know what it means. What do you think my question should be? – Michael Fox Oct 30 '14 at 22:07
  • @ChristianConkle. Thanks. That way if someone pastes the error into Google they'll have a chance of finding this. – Michael Fox Oct 30 '14 at 22:31

2 Answers2

4

You just can't mix operators of that fixity. Fixity in Haskell is just operator precedence, just like your "Please Excuse My Dear Aunt Sally" (if you're American, this is probably what you learned) for remembering what order to apply operations in, i.e. Parentheses, Exponent, Multiplication, Division, Addition, Subtraction. Here, the .~ operator is said to have a higher right associative precedence than the left associative low precedence of the & operator. The real problem comes from mixing the right and left associative operators, the compiler doesn't know what order to apply them in!

Instead, you can re-formulate it as two operator sections composed together

fmap ((myRecord &) . (_2 . someField .~)) double

So that you give the compiler an explicit grouping, or you can use the prefix set function for a cleaner look

fmap (\v -> set (_2 . someField) v myRecord) double

Or if you want to get rid of the lambda (my preference is to leave it alone), you can use flip as

fmap (flip (set (_2 . someField)) myRecord) double
bheklilr
  • 53,530
  • 6
  • 107
  • 163
2

This is a somewhat strange restriction on the way operators can be used in sections.

Basically, when you have an operator section like (e1 & e2 .~), there are two ways you can imagine desugaring it to sectionless notation. One is to turn the operator into prefix position:

(.~) (e1 & e2)

(If the operator were in front, a flip would also need to be added.)

The other way is to turn it into a lambda expression:

\x -> e1 & e2 .~ x

These two ways of thinking of sections are supposed to give the same result. There's a problem, though, if as here, there is another operator & of lower fixity/precedence than the sectioned operator. Because that means the lambda expression parses as

\x -> e1 & (e2 .~ x)

In other words, the lambda expression definitely isn't equivalent to simply moving the operator and keeping the rest as a unified expression.

While the Haskell designers could have chosen to interpret a section in one of the two ways, instead they chose to disallow sections where the two interpretations don't match, and make them errors. Possibly because, as I understand it, the lambda expression interpretation is more intuitive to humans, while the operator movement is easier to implement in a parser/compiler.

You can always use the lambda expression explicitly, though, or make your own point-free version like @bheklilr showed.

Ørjan Johansen
  • 18,119
  • 3
  • 43
  • 53
  • Why do we have the same problem for (0:1:) where the lambda expression \x -> 0:1:x is equivalent ? – codeshot Dec 22 '17 at 20:50
  • @codeshot No, `(:) (0:1)` would need to be equivalent to `\x -> 0:1:x`, but it's actually `\x -> (0:1):x` (which doesn't make sense). – Ørjan Johansen Dec 22 '17 at 21:38