Skip to content

toastal/return-optics

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

17 Commits
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

Return.Optics

Return.Optics is a utility library extending Return with Monocle making a clean, concise API for doing Elm component updates in the context of other updates. Initially it includes helper functions around refraction—the bending of light. Like viewing a straw being inserted into a glass of water, we’ll use a Lens to bend our top-level update function into our component update, and when we pull it out, well be left with an unbent ( model, Cmd msg ) of the Elm architecture.

If you would like a more in-depth read into why, you can read about that on my blog.

However, if that’s not your thing and doesn’t make sense, you’re in luck because we’re about to go over an example.

Suppose we have this trivial, toy component and model…

Models

module Model exposing (Model)

import Checkbox.Model as Checkbox


type alias Model =
    { pageTitle : String
    , checkbox : Checkbox.Model
    }
module Checkbox.Model exposing (Model)

type alias Model =
    { checked : Bool
    }

Msgs

module Msg exposing (Msg(..))

import Checkbox.Msg as Checkbox


type Msg
    = TitleChange String
    | CheckboxMsg Checkbox.Msg
module Checkbox.Msg exposing (Msg(..))

type Checkbox
    = CheckMe Bool

Assuming we have built up some cmdWeAlwaysDo, with the standard library we’d write updates like this:

Stardard Updates

module Update exposing (update)

import Checkbox.Update as Checkbox
import Model
import Msg exposing (Msg(TitleChange, CheckboxMsg))


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    let
        cmdWeAlwaysDo : Cmd Msg
        cmdWeAlwaysDo =
            -- insert a real command in a non-toy app
            Cmd.none
    in
        case msg of
            TitleChange title ->
                ( { model | pageTitle = title }, cmdWeAlwaysDo )

            CheckboxMsg cbMsg ->
                let
                    ( cbModel, cbCmd )
                        Checkbox.Update cbMsg model.checkbox
                in
                    { model | checkbox = cbModel }
                        ! [ cbCmd
                          , cmdWeAlwaysDo
                          ]
module Checkbox.Update exposing (update)

import Checkbox.Model as Model
import Checkbox.Msg as Msg exposing (Msg(CheckMe))


update : Msg -> Model -> ( Model, Cmd Msg )
update msg model =
    case msg of
        CheckMe bool ->
            { model | checked = bool }

Using Return.Optics.refractl and Lenses we can instead change our model files and our update files like this:

module Model exposing (..)

import Monocle.Lens exposing (Lens)
import Checkbox.Model as Checkbox


type alias Model =
    { pageTitle : String
    , checkbox : Checkbox.Model
    }


pageTitlel : Lens Model String
pageTitlel =
    Lens .pageTitle (\p m -> { m | pageTitle = p })


checkboxl : Lens Model Checkbox.Model
checkboxl =
    Lens .checkbox (\c m -> { m | checkbox = c })
module Checkbox.Model exposing (..)

import Monocle.Lens exposing (Lens)


type alias Model =
    { checked : Bool
    }


checkedl : Lens Model Bool
checkedl =
    Lens .checked (\c m -> { m | checked = c })
module Update exposing (update)

import Return exposing (Return)
import Return.Optics exposing (refractl)
import Checkbox.Update as Checkbox
import Model
import Msg exposing (Msg(TitleChange, CheckboxMsg))


update : Msg -> Model -> Return Msg Cmd
update msg =
    let
        cmdWeAlwaysDo : Cmd Msg
        cmdWeAlwaysDo =
            -- insert a real command in a non-toy app
            Cmd.none
    in
        Return.singleton
            >> Return.command cmdWeAlwaysDo
            >> case msg of
                TitleChange title ->
                    Return.map (.set Model.pageTitlel title)

                -- Note how much more condensed this part is
                CheckboxMsg cbMsg ->
                    refractl Model.checkboxl CheckboxMsg (Checkbox.update cbMsg)
module Checkbox.Update exposing (update)

import Checkbox.Model as Model
import Checkbox.Msg as Msg exposing (Msg(..))


update : Msg -> Model -> Return Msg Model
update msg =
    Return.singleton
        >> case msg of
            CheckMe bool ->
                Return.map (.set Model.checkedl bool)