laserpants / elm-update-pipeline / Update.Pipeline

Sequential composition of updates facilitated by the pipe operator.

Basics

save : a -> ( a, Platform.Cmd.Cmd msg )

Turn a model value into a ( model, Cmd msg ) pair without adding any commands.

save model =
    ( model, Cmd.none )

map : (a -> b) -> ( a, Platform.Cmd.Cmd msg ) -> ( b, Platform.Cmd.Cmd msg )

Apply a function to the model (i.e. first component) of a ( model, Cmd msg ) pair. Partially applied, we can also think of this as taking a function a -> b and lifting it into one of type ( a, Cmd msg ) -> ( b, Cmd msg ).

addCmd : Platform.Cmd.Cmd msg -> a -> ( a, Platform.Cmd.Cmd msg )

Create a ( model, Cmd msg) pair from the two arguments. The implementation of this function is not very exciting — it is simply defined as addCmd cmd model = ( model, cmd ) — but it can still be quite useful in idiomatic code. For example, one could write

{ model | power = 100 }
    |> addCmd someCmd
    |> andThen (setStatus Done)

… instead of

( { model | power = 100 }, someCmd )
    |> andThen (setStatus Done)

See also andAddCmd.

mapCmd : (msg1 -> msg2) -> ( a, Platform.Cmd.Cmd msg1 ) -> ( a, Platform.Cmd.Cmd msg2 )

Transform the message produced by the command inside a ( model, Cmd msg ) pair.

join : ( ( a, Platform.Cmd.Cmd msg ), Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

Remove one level of structure that results from composing functions of the form a -> ( b, Cmd msg ):

f : a -> ( b, Cmd msg )
g : b -> ( c, Cmd mgs )

map g << f : a -> ( ( c, Cmd msg ), Cmd msg )

It is useful to know that andThen is defined as \f -> join << map f.

equals : ( a, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg ) -> Basics.Bool

Compare the models of two ( model, Cmd msg) values.

Note that the presence of effects means that

> (save 9) == (map2 (^) (save 3) (save 2))
False : Bool

This function can be used for comparison when only the model is of interest:

> equals (save 9) (map2 (^) (save 3) (save 2))
True : Bool

Chaining Updates

These functions enable composition of updates by chaining together functions of the type a -> ( b, Cmd msg ).

andThen : (b -> ( a, Platform.Cmd.Cmd msg )) -> ( b, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

When used in conjunction with the pipe operator, this combinator extracts the model from a ( model, Cmd msg ) value and passes it as input to the next function in a pipeline. For example;

model
    |> setPower 100
    |> andThen (setDone True)

Monadic functions of type a -> ( b, Cmd msg ) are the building blocks of a pipeline. For instance, the type of setPower in the above example is Int -> Model -> ( Model, Cmd Msg ).

sequence : List (a -> ( a, Platform.Cmd.Cmd msg )) -> a -> ( a, Platform.Cmd.Cmd msg )

Take a list of a -> ( a, Cmd msg ) functions and run them sequentially, in a left-to-right manner, with the second argument as input.

when : Basics.Bool -> (a -> ( a, Platform.Cmd.Cmd msg )) -> a -> ( a, Platform.Cmd.Cmd msg )

Run an update if the given condition is True, otherwise do nothing. For example;

model
    |> when (power > 100) (setWarning Overflow)

See also andThenIf.

kleisli : (b -> ( c, Platform.Cmd.Cmd msg )) -> (a -> ( b, Platform.Cmd.Cmd msg )) -> a -> ( c, Platform.Cmd.Cmd msg )

Right-to-left composition of two functions that return ( model, Cmd msg ) values, passing the first component of the first return value as input to the second function.

This is analogous to ordinary function composition in the following way:

(<<) : (b -> c) -> (a -> b) -> a -> c

kleisli : (b -> ( c, Cmd msg )) -> (a -> ( b, Cmd msg )) -> a -> ( c, Cmd msg )

Applicative Interface

These functions address the need to map functions with more than one parameter over ( model, Cmd msg ) inputs.

map2 : (p -> q -> r) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg )

Combine two ( model, Cmd msg ) values by applying a function of two arguments to their respective models.

map3 : (p -> q -> r -> s) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg ) -> ( s, Platform.Cmd.Cmd msg )

Combine three ( model, Cmd msg ) values by applying a function of three arguments to their respective models.

map4 : (p -> q -> r -> s -> t) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg ) -> ( s, Platform.Cmd.Cmd msg ) -> ( t, Platform.Cmd.Cmd msg )

Combine four ( model, Cmd msg ) values by applying a function of four arguments to their respective models.

map5 : (p -> q -> r -> s -> t -> u) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg ) -> ( s, Platform.Cmd.Cmd msg ) -> ( t, Platform.Cmd.Cmd msg ) -> ( u, Platform.Cmd.Cmd msg )

Combine five ( model, Cmd msg ) values by applying a function of five arguments to their respective models.

map6 : (p -> q -> r -> s -> t -> u -> v) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg ) -> ( s, Platform.Cmd.Cmd msg ) -> ( t, Platform.Cmd.Cmd msg ) -> ( u, Platform.Cmd.Cmd msg ) -> ( v, Platform.Cmd.Cmd msg )

Combine six ( model, Cmd msg ) values by applying a function of six arguments to their respective models.

map7 : (p -> q -> r -> s -> t -> u -> v -> w) -> ( p, Platform.Cmd.Cmd msg ) -> ( q, Platform.Cmd.Cmd msg ) -> ( r, Platform.Cmd.Cmd msg ) -> ( s, Platform.Cmd.Cmd msg ) -> ( t, Platform.Cmd.Cmd msg ) -> ( u, Platform.Cmd.Cmd msg ) -> ( v, Platform.Cmd.Cmd msg ) -> ( w, Platform.Cmd.Cmd msg )

Combine seven ( model, Cmd msg ) values by applying a function of seven arguments to their respective models.

andMap : ( a, Platform.Cmd.Cmd msg ) -> ( a -> b, Platform.Cmd.Cmd msg ) -> ( b, Platform.Cmd.Cmd msg )

Trying to map a function (+) : number -> number -> number over two ( model, Cmd msg) inputs; first applying it to the first value

map (+) (save 4)

… we end up with a result of type ( (number -> number), Cmd msg ). To apply the function inside this value to another ( number, Cmd msg ) value, we use this function in the following way:

map (+) (save 4) |> andMap (save 5)

In elm repl, we can verify that the result is what we expect:

> Tuple.first <| (map (+) (save 4) |> andMap (save 5))
9 : number

This pattern scales in a nice way to functions of any number of arguments.

See also map2, map3, etc. If not sooner, you’ll need this function when you want to mapN for N > 7.

Pointfree Helpers

A note about pointfree style and η-reduction

Thanks to currying, in Elm we can often omit function arguments:

f1 x = g x      <==>  f1 = g
f2 x = g (h x)  <==>  f2 = g << h

Making the arguments implicit in this way allows the programmer to think about the program more abstractly, and can (sometimes) lead to more readable program code.

Pointfree:

update : Msg -> Model -> ( Model, Cmd Msg )
update msg =
    case msg of
        ButtonClicked ->
            setMessage "The button was clicked!"
                >> andThen haveCoffee

Pointful:

update msg model =
    case msg of
        ButtonClicked ->
            model
                |> setMessage "The button was clicked!"
                |> andThen haveCoffee

using : (a -> a -> b) -> a -> b

This combinator is useful for writing code in pointfree style.

Consider the following example:

gotoPage : Int -> Model -> ( Model, Cmd msg )
gotoPage = ...

nextPage model =
    gotoPage (model.currentPage + 1) model

Using this helper, the above code can be refactored as

nextPage =
    using (\{ currentPage } -> gotoPage (currentPage + 1))

See also with, andUsing.

with : (a -> b) -> (b -> a -> c) -> a -> c

This combinator is useful for writing code in pointfree style.

For example, the following code;

model
    |> updateSomething
    |> andThen (\newModel -> setCounterValue (newModel.counter + 1) newModel)

… can be refactored as

model
    |> updateSomething
    |> andThen (with .counter (setCounterValue << (+) 1))

See also using, andWith.

Shortcuts

andAddCmd : Platform.Cmd.Cmd msg -> ( a, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

This function is a shortcut for andThen <<addCmd.

Example use:

model
    |> save
    |> andAddCmd someCmd

andUsing : (b -> b -> ( a, Platform.Cmd.Cmd msg )) -> ( b, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

Shortcut for andThen <<using.

andWith : (b -> c) -> (c -> b -> ( a, Platform.Cmd.Cmd msg )) -> ( b, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

Shortcut for \view -> andThen <<withview.

andThenIf : Basics.Bool -> (a -> ( a, Platform.Cmd.Cmd msg )) -> ( a, Platform.Cmd.Cmd msg ) -> ( a, Platform.Cmd.Cmd msg )

This function is a shortcut for \cond -> andThen <<whencond.

model
    |> save
    |> andThenIf (power > 100) (setWarning Overflow)