How do you guys deal with monads?

For monad heavy code I tend to use a modified elm-format[1] that doesn’t indent code after <| \foo ->.[2]
This allows me to use do m fn = andThen fn m like this:

PrefixOperator operator ->
    State.do (StateLookup.findModuleOfVar files thisFile Nothing operator) <| \moduleName ->
    State.do (State.addVarType moduleName operator type_) <| \() ->
    State.do State.getNextIdAndTick <| \firstArgId ->
    State.do State.getNextIdAndTick <| \secondArgId ->
    State.do State.getNextIdAndTick <| \resultId ->
    finish ...

  1. Can’t remember where I coped it from though, sorry to the original author! ↩︎

  2. This means I use the tweaked elm-format in the whole repository or not at all. Anything in between would be madness :slight_smile: ↩︎

Oh cool, that’s at least a good work around for using the elm-do package. Still feels weird using it for Elm though, since most other people in the community won’t understand it.

To add an approach I haven’t seen mentioned yet, I’d tend to write the code like this:

extractTag : String -> Result (List Parser.DeadEnd) ( Tag, String )
extractTag str =
    let
        openTagResult =
            Parser.run openTagParser str

        insideResult =
            Result.andThen
                (\open ->
                    Parser.run
                        (insideStringToParser open.name)
                        ("<" ++ open.name ++ ">" ++ open.afterOpenTag)
                )
                openTagResult

        finalResult =
            Result.map2
                (\open ( inside, rest ) ->
                    ( { name = open.name
                      , attributes = open.attributes
                      , inside = inside
                      }
                    , rest
                    )
                )
                openTagResult
                insideResult
    in
    finalResult
Example of the same approach with the `(, Cmd msg)` monad
update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        ( newA, aCmd ) =
            updateA msg model.a

        ( newB, bCmd ) =
            updateB msg newA model.b
    in
    ( { model | a = newA, b = newB }
    , Cmd.batch [ aCmd, bCmd ]
    )

The downside is it obviously takes a huge amount of vertical space. Also it doesn’t give a clean pipeline ordering to the flow of information. But I think there are some benefits:

  • it (arguably) makes it easier to focus in detail on what each single step of the code does (and I have some minimal evidence to think that this is less daunting for new developers to understand what’s going on)
  • it allows for even more flexibility in exactly how any errors are combined (though admittedly that’s rarely needed)
  • it lets you have more of a branching structure of data dependencies (as opposed to being required to thread all data through a single pipeline sequence–though again, that kind of flexibility is rarely needed).

Though I agree, I wish there were consensus on a go-to approach for doing this sort of thing in Elm. All the available solutions seem very clunky compared to do-notation.

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