Compiler: Matching on negative literals results in a parser error

Created on 30 Aug 2018  Â·  15Comments  Â·  Source: elm/compiler

in elm 0.19:

isMinusOne : Int -> Bool
isMinusOne val =
   case val of
       -1 ->
           True

       _ ->
           False

Results in:

parse error
Line 4, Column 8
Something went wrong while parsing a `case` expression in isMinusOne's
definition.

3|    case val of
4|        -1 ->
          ^
I was expecting to see a pattern, like `name` or (Just x)

If parentheses are added, which were needed in elm 0.18:

parse error
Line 4, Column 9
Something went wrong while parsing an expression (in parentheses) in
isMinusOne's definition.

4|        (-1) ->
           ^
I was expecting:

  - a pattern, like `name` or (Just x)
  - a closing paren

Link to ellie example

Most helpful comment

Why are you doing this? Instead of fake examples, can you explain how this comes up?

I'm imagining that an enum like type Mode = Print | Pdf | Html is getting represented as integers for unclear reasons. What is the real situation?

All 15 comments

I think it's quite an important issue as we spent about 30% of our migration time from Elm 18 to Elm 19 fixing negative number pattern matches in our code and I see no practical reason why it's not working.

Current bypasses are also looking quite ugly:

case i of
   1 ->
        ...
   2 ->
        ...
   i ->
       if i == -1 then
           ...
       else if i == -2 then
          ...
       else
          ...

or

case i of
   1 ->
        ...
   2 ->
        ...
   i ->
      case negate i of
          1 ->
              ...
          2 ->
               ...
          _ ->
              ...

We use

 case (num + 1) of
        0 ->

        1 ->

        2 ->

Which is not ugly but very WAT?-like and all entries need to be changed...
Nevertheless it somehow amuses me.

Why are you doing this? Instead of fake examples, can you explain how this comes up?

I'm imagining that an enum like type Mode = Print | Pdf | Html is getting represented as integers for unclear reasons. What is the real situation?

@evancz at very least we use such matches for decoding (converted to strings for now):

{-| Convert string value to StageRank
-}
rankFromValue : String -> StageRank
rankFromValue rank =
    case rank of
        "-2" ->
            StageRankBad

        "-1" ->
            StageRankFlawed

        "0" ->
            StageRankNormal

        "1" ->
            StageRankMinor

        "2" ->
            StageRankMajor

        "3" ->
            StageRankGreat

        _ ->
            StageRankInvalid

@evancz in my app, I am testing the difference between two numbers, which indicate the positions of two values in a list. If the difference is exactly one or exactly negative one (indicating left-hand or right-hand adjacency), then the app should do something special (different in each case), and if the difference is any other value then the app should do nothing. So the code looked like:

case from - to of
    (-1) -> -- left-hand adjacent
    1 -> -- right hand adjacent
    _ -> -- do nothing

I've replaced this with

    difference = from - to
in
    if difference == -1
    then -- left-hand adjacent
    else
        if difference == 1
        then -- r.h. adjacent
        else -- do nothing

Which works, but at the cost of clarity and concision.

@evancz In my app, I'm letting users upvote (+1), downvote (-1), and undo a vote (0) on comments like on Reddit, and I do have a type like so...

type VoteDirection
    = None
    | Up
    | Down

but the use case here is trying to decode it from JSON integers.

To add another real world example, the button property of a PointerEvent represents having no buttons pressed with a -1. I encountered this while making a decoder as follows:

```elm
type Button
= NoButton
| MainButton
-- ...

buttonDecoder : JD.Decoder Button
buttonDecoder =
let
buttonFromInt int =
case int of
(-1) ->
NoButton

            0 ->
                MainButton

            -- ...

in
JD.map buttonFromInt JD.int

```

@evancz In our McMaster outreach program Graphics library, we give the students an (x,y) vector as a tuple representing arrow keys where, for example, (-1,0) means the left arrow key is pressed. It's convenient to have this be able to be matched by a case expression in their code. We were surprised that all the sudden our game code didn't compile in 0.19 so this is definitely an annoyance.

case x of c would be preferred as a language feature, case msg of Msg just the byproduct.

@evancz I have a real world example too. This is my code that used to work with elm 0.18:

        newLocationsWithCastling =
          case pieceLocation.piece of
            Piece.WhiteKing ->
              case destSquare - departSquare of
                (-2) ->
                  Dict.insert (BB.toString BB.WhiteRooks)
                    { bb =
                      BB.or (BB.and whiteRooksBb (BB.complement (BB.fromSquares [ 0 ]))) (BB.shiftRight departLocBb 1)
                    , piece = Piece.WhiteRook
                    }
                    newLocations

                2 ->
                  Dict.insert (BB.toString BB.WhiteRooks)
                    { bb =
                      BB.or (BB.and whiteRooksBb (BB.complement (BB.fromSquares [ 7 ]))) (BB.shiftLeft departLocBb 1)
                    , piece = Piece.WhiteRook
                    }
                    newLocations

                _ ->
                  newLocations

            Piece.BlackKing ->
              case destSquare - departSquare of
                (-2) ->
                  Dict.insert (BB.toString BB.BlackRooks)
                    { bb =
                      BB.or (BB.and blackRooksBb (BB.complement (BB.fromSquares [ 56 ]))) (BB.shiftRight departLocBb 1)
                    , piece = Piece.BlackRook
                    }
                    newLocations

                2 ->
                  Dict.insert (BB.toString BB.BlackRooks)
                    { bb =
                      BB.or (BB.and blackRooksBb (BB.complement (BB.fromSquares [ 63 ]))) (BB.shiftLeft departLocBb 1)
                    , piece = Piece.BlackRook
                    }
                    newLocations

                _ ->
                  newLocations

            _ ->
              newLocations

where I have to detect in chess whether the King moved 2 squares on the left or 2 on the right (castling) and to move the Rook accordingly.
Of course I can use a work around but common sense and mathematical convention orders that when you are at a point which you define as the origin assigning it a value of 0 then moving e.g. 2 meters on the left is -2 from you current location and moving 2 meters on the right is +2 or 2.
Why should we consider that negative numbers are of any difference of positive ones and treat them differently? I cannot see any practical reason.
In my opinion it's a pity for elm not to support negative numbers in pattern matching

@Voronchuk hi, you can save a nesting level by matching on a pair of (i, negate i), e.g.

test : Int -> Int
test i = case (i, negate i) of
  (_, 2) -> 0
  (_, 1) -> 0
  (0, _) -> 1
  (1, _) -> 1
  _ -> 2

@yawaramin Good point. Still seems unnecessarily complicated in my opinion (and probably slightly slower)!

I've bumped into this as well, trying to decode a JSON payload where -1 is used as a flag.

But practical examples aside, it just seems odd in principle that the language will allow you to pattern match against integers, unless those integers happen to be negative.

We've run into this speed bump in our 0.18 → 0.19 porting efforts as well. Here's our real-world example:

ratingValueToCssClass ratingValue =
    case ratingValue of
        (-1) ->
            .unrated

        0 ->
            .field0

        1 ->
            .field1

        2 ->
            .field2

        3 ->
            .field3

        4 ->
            .field4

        5 ->
            .field5

        _ ->
            .invalidRating

This could certainly be replaced by decoding our JSON API to a richer type than an integer, but we'd probably just end up with the same issue in our JSON decoder.

I use a modular scale for typography, and I need to match certain integers in this scale, which can be above or below 0.

I understand the desire to help developers avoid bad patterns by using custom types, but it appears quite arbitrary to assume that matching on negative numbers is undesirable, or even that an ideal program should not do this. At best one could assume it shouldn't happen too often.

If the principle is to keep it simple and encourage good patterns, it appears to me that a syntax error is inappropriate, as it is not at all obvious why should one avoid it.

Was this page helpful?
0 / 5 - 0 ratings