# Useful Chompers

**URL:** <https://discourse.elm-lang.org/t/useful-chompers/9277>\
**Category:** Show and Tell\
**Created:** [August 16, 2023, 9:23am UTC](https://discourse.elm-lang.org/t/useful-chompers/9277 "2023-08-16T09:23:00Z")\
**Posts on this page:** 9\
**Page:** 1

<div class="post-metadata">

**Author:** ![dwayne](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dwayne/32/5478_2.png) [@dwayne](https://discourse.elm-lang.org/u/dwayne)\
**Post date:** [August 16, 2023, 9:23am UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/1 "2023-08-16T09:23:00Z")

</div>

[Useful Chompers](https://dev.to/dwayne/useful-chompers-lo5)

Learn about **useful chompers** that can help you write better parsers.

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [August 16, 2023, 9:54pm UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/2 "2023-08-16T21:54:12Z")

</div>

Interesting post.

I love parser combinators, I’ve learnt a bit about them via OCaml (just dabbling).

One thing I noticed is that it’s pretty much the only time where I’d make sens to me to actually introduce a runtime error, to make the program fail loud and clear (when something goes horribly horribly wrong).

Because it seems to me that some operations can’t be expressed correctly by the type system when parsing.

For instance, in your decimal function, you use `Maybe.withDefault` to satisfy the type system’s constraints. But that constraint is a little bit artificial, because you do know that only floats will come out of `chompDecimal`.

```elm
decimal : Parser Float
decimal =
    chompDecimal
        |> P.mapChompedString (\s () -> String.toFloat s |> Maybe.withDefault 0)
        |> lexeme

```

So in this specific case, it feels to me that satisfying the type system actually gives no value (it’s extra work for zero benefit)

This is what I’d prefer to write for clarity. But even then I’m forced to represent that same branch that’ll actually never trigger.

```auto
decimal : Parser Float
decimal =
    chompDecimal
        |> P.getChompedString
        |> P.andThen
            (\s ->
                case String.toFloat s of
                    Just n ->
                        P.succeed n

                    Nothing ->
                        P.problem "impossible"
            )

```

Any thoughts?

---

<div class="post-metadata">

**Author:** ![dwayne](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dwayne/32/5478_2.png) [@dwayne](https://discourse.elm-lang.org/u/dwayne)\
**Post date:** [August 17, 2023, 3:07am UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/3 "2023-08-17T03:07:00Z")

</div>

I understand your point and it would be nice if there was a way around it in Elm. But there isn’t so I don’t lose sleep over it.

When I was writing `elm-natural` I designed the API so that the `Maybe.withDefault` was hidden in cases where the user knew for certain they had valid input. See for e.g. [fromSafeInt](https://github.com/dwayne/elm-natural/blob/115796ab88ae39baef9ca455376fc746f391e461/src/Natural.elm#L239-L262) and [fromSafeString](https://github.com/dwayne/elm-natural/blob/115796ab88ae39baef9ca455376fc746f391e461/src/Natural.elm#L399-L421).

So if you were to write a `natural` parser it could look like:

```elm
natural : Parser Natural
natural =
    chompNatural
        |> P.mapChompedString (\s () -> Natural.fromSafeString s)
        |> lexeme

```

BTW, I like your `decimal` version better because in the off chance the conversion does fail you get a clear parser error (assuming you improve the problem message).

---

<div class="post-metadata">

**Author:** ![benjamin-thomas](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/benjamin-thomas/32/3869_2.png) [@benjamin-thomas](https://discourse.elm-lang.org/u/benjamin-thomas)\
**Post date:** [August 17, 2023, 5:18am UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/4 "2023-08-17T05:18:41Z")

</div>

Of course, it’s not a “big” problem I wouldn’t loose sleep over it either 😉

I thought I’d post because maybe someone knows a trick or two I could learn about.

You say you don’t know a way around this in Elm, but presumably there is in another language?

I’m wondering because from the type system’s perspective, all makes sens, but we are left with something not ideal in the end.

`fromSafeString` looks like a useful convention, I like it.

I thought about it a bit more and I think we can also generalize the problem like this:

```elm
{-| `fromMaybe` assumes that applying `f` will never return `Nothing`. 
If it does, the parsing fails.
-}
fromMaybe : (a -> Maybe b) -> a -> Parser b
fromMaybe f =
    f
        >> Maybe.map P.succeed
        >> Maybe.withDefault (P.problem "Bug: f produced Nothing!")

decimal : Parser Float
decimal =
    chompDecimal
        |> P.getChompedString
        |> P.andThen (fromMaybe String.toFloat)

```

Then I feel things are slightly better that way.

---

<div class="post-metadata">

**Author:** ![dwayne](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dwayne/32/5478_2.png) [@dwayne](https://discourse.elm-lang.org/u/dwayne)\
**Post date:** [August 17, 2023, 2:08pm UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/5 "2023-08-17T14:08:27Z")

</div>

> [@benjamin-thomas](#):
>
> You say you don’t know a way around this in Elm, but presumably there is in another language?

Yes, in Haskell you can do it with a [partial function](https://wiki.haskell.org/Partial_functions). For e.g. if you know for sure you’re dealing with a non-empty list at some point in your application then you can use [head](https://hackage.haskell.org/package/base-4.18.0.0/docs/Prelude.html#v:head) without having to handle a `Maybe` result.

```haskell
head :: [a] -> a
head (x:_) = x

```

---

<div class="post-metadata">

**Author:** ![pit](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/pit/32/5389_2.png) [@pit](https://discourse.elm-lang.org/u/pit)\
**Post date:** [August 19, 2023, 8:34pm UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/6 "2023-08-19T20:34:17Z")

</div>

Thank you for the article, Dwayne.

If you want to improve performance even more, you could use the following approach:

- Use `Parser.getOffset` and `Parser.getSource` and then `String.dropLeft` to get the string that hasn’t been parsed yet
- Implement the checks of the chompers without using the `Parser` module, for example via loops with `String.uncons`
- In the case of success, advance the parser with `Parser.token`, giving it the parsed text as the token

How to handle the failure case depends upon which error you want to generate at which position. One solution would be to advance the parser to the failing position with `Parser.token` like in the success case, followed by a `Parser.chompIf isGood`.

If you don’t need the error to be reported at the exact first failing position, I found that you can improve performance even more for chompers with a given lower bound (`chompAtLeast`, `chompBetween`, and `chompExactly`) by using `String.all isGood` for the part up to the lower bound instead of a loop.

This is just a very brief description. I can send you a sample implementation, if you like.

Thank you again for the article and the code.

---

<div class="post-metadata">

**Author:** ![dwayne](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/dwayne/32/5478_2.png) [@dwayne](https://discourse.elm-lang.org/u/dwayne)\
**Post date:** [August 23, 2023, 10:33am UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/7 "2023-08-23T10:33:57Z")

</div>

Thanks for finding these interesting ways to improve performance. I’d have to try them out myself at some point but also feel free to [share your sample implementation](https://github.com/dwayne/elm-chompers/issues/new).

---

<div class="post-metadata">

**Author:** ![pit](https://yyz1.discourse-cdn.com/flex035/user_avatar/discourse.elm-lang.org/pit/32/5389_2.png) [@pit](https://discourse.elm-lang.org/u/pit)\
**Post date:** [August 24, 2023, 5:48pm UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/8 "2023-08-24T17:48:36Z")

</div>

[Done](https://github.com/dwayne/elm-chompers/issues/1) 🙂

---

<div class="post-metadata">

**Author:** ![system](https://canada1.discourse-cdn.com/flex035/uploads/elm_lang/original/1X/50a05e53677a2c3b47776d7abd0f113eb50193a1.png) [@system](https://discourse.elm-lang.org/u/system)\
**Post date:** [September 3, 2023, 5:49pm UTC](https://discourse.elm-lang.org/t/useful-chompers/9277/9 "2023-09-03T17:49:30Z")

</div>

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