[elm/parser]: understanding what's going on

First off, apologies for length of this question.

I’m having a problem writing parser functions, and it’s exposed a limitation in my understanding of how they work. So, first order, I’d appreciate any help resolving this specific problem. Second order though, any insight into how to think about parser operation more generally would be much appreciated.

Specific Problem

I need to handle attribute declarations, for which there are two variants:

  1. name: Type
  2. name: Type = initialValue

I followed the pattern in this post to come up with the following:

type alias Attribute =
    { name : String
    , type_ : String
    , value : Maybe String
    }

attributeParser : Parser Attribute
attributeParser =
    Parser.succeed identity
        |= parseLowerCaseName
        |. Parser.spaces
        |. Parser.symbol ":"
        |. Parser.spaces
        |= parseUpperCaseName
        |> Parser.andThen attributeInitialValueParser

attributeInitialValueParser : String -> String -> Parser Attribute
attributeInitialValueParser name type_ =
    Parser.oneOf
        [ Parser.succeed (Attribute name type_)
            |. Parser.spaces
            |. Parser.symbol "="
            |. Parser.spaces
            |= (Parser.map Just parseValue)
        , Parser.succeed (Attribute name type_ Nothing)
        ]

That doesn’t type check. Intuitively, I think it’s because attributeParser is pushing 2 values into the pipe (the parsed name & type), but (1) that’s not compatible with Parser.succeed identity and (2) I’m not convinced the type signature for attributeInitialValueParser is correct. Perhaps it should take a tuple (String, String) instead of two separate params?

Any help on that appreciated. Thanks.

General Problem

Hitting this highlighted I don’t understand the parsing mechanism as well as I thought I did. I read through the source, but it’s quite abstract and I’ll admit was a bit of a challenge for an elm newbie. So I don’t feel that much better placed. Intuitively, I understand that the pipeline functions (notably |= & |.) facilitate combination. When run, the input string is passed to each Parser function. Those functions do one or both of the following:

  • Consume characters from the input stream
  • Add parsed elements to the “output stream” (recognising this isn’t really a stream).

However I don’t yet have a good mental model for how to think about this more formally. For example: the compiler error on the code above says:

The 1st argument to `andThen` is not what I expect:

101|         |> Parser.andThen attributeInitialValueParser
                               ^^^^^^^^^^^^^^^^^^^^^^^^^^^
This `attributeInitialValueParser` value is a:

    String -> Parser b

But `andThen` needs the 1st argument to be:

    String -> Parser Attribute

But the type signature for attributeInitialValueParser is String -> String -> Parser Attribute.

This is already quite long so I’ll stop here. Any insight appreciated.

Thanks.

Your intuition was good.

Check the signatures of identity and Parser.andThen:

identity: a -> a
andThen : (a -> Parser b) -> Parser a -> Parser b

Both identity and the fist parameter of andThen, which is a function, use a single argument.

But in attributeParser, identity will receive two arguments (ignoring the details of currying for a moment), as there are two |= in the pipeline, and the result will be passed to attributeInitialValueParser. This won’t work.

You could use:

attributeParser : Parser Attribute
attributeParser =
    Parser.succeed Tuple.pair
    ...

then

attributeInitialValueParser : (String, String) -> Parser Attribute
attributeInitialValueParser (name, type_) =

This way, you have a single value in the first part, a tuple, passed to the second part.

@dmy thanks for your help once again.

Reflecting on your response and trying to answer the second part of my question then in very rudeimentary terms:

  • The first line in the parser implementation sets the “shape” of what will be captured if the parser succeeds.
  • So succeed Tuple.pair says the sequence of parsing actions will generate a pair of values, whereas succeed identity says only a single value will be captured
  • More formally, Tuple.pair is a constructor function that takes 2 parameters and returns a tuple. (Its type signature is a -> b -> (a, b). Whereas the type of identity is a -> a - so it only consumes a single value (and returns it).

I’ll think more about the values being passed to andThen so I’m clear how it’s working. Meantime, thanks again.

1 Like

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