Core concepts

State & lenses

miso bundles a lightweight lens library in Miso.Lens to minimise dependencies and payload size. Any lens library (optics, lens) also works — Miso.Lens is not required.

Basic operations

view l              -- read a field (MonadReader)
set  l v            -- write a field
over l f            -- modify a field
r ^. l              -- infix read
r & l .~ v          -- infix write

MonadState operators (inside Effect)

l .= v    -- set a field
l %= f    -- modify a field
l += n    -- increment a numeric field
l -= n    -- decrement
l *= n    -- multiply

this — the identity lens

When the model is the field (e.g. the model is a plain Int), use this:

update = \case
  Increment -> this += 1
  Decrement -> this -= 1

Generating lenses

Three approaches, pick one:

Template Haskell

{-# LANGUAGE TemplateHaskell #-}
import Miso.Lens.TH (makeLenses)

data Model = Model { _count :: Int, _name :: MisoString }
makeLenses ''Model

update = \case
  Increment -> count += 1
  Rename n  -> name .= n

Generics

Miso.Lens.Generic.field / HasLens derive lenses at compile time using GHC.Generics — no splice required. Needs TypeApplications and, optionally, OverloadedLabels for the #field shorthand:

{-# LANGUAGE DataKinds, DeriveGeneric #-}
{-# LANGUAGE OverloadedLabels        #-}
{-# LANGUAGE TypeApplications        #-}
import GHC.Generics (Generic)
import Miso.Lens.Generic (field)

data Model = Model
  { count :: Int
  , name  :: MisoString
  } deriving (Eq, Generic)

update = \case
  Increment -> field @"count" += 1
    -- via TypeApplications
  Rename n  -> #name .= n
    -- via OverloadedLabels

Hand-written

name :: Lens Person MisoString
name = lens _name $ \p n -> p { _name = n }

Try it

Updating a record model through lenseslive

Ada is 36 years old

data Person = Person
  { _name :: MisoString
  , _age  :: Int
  } deriving (Show, Eq)

-- Hand-written lenses (Miso.Lens.TH and
-- Miso.Lens.Generic can write these).
name :: Lens Person MisoString
name = lens _name $ \p n -> p { _name = n }

age :: Lens Person Int
age = lens _age $ \p a -> p { _age = a }

data PersonAction
  = Rename MisoString
  | Birthday
  | Younger

person
  :: Component ctx () Person PersonAction
person = component (Person "Ada" 36) update view
  where
    update = \case
      Rename n -> name .= n
        -- set through a lens
      Birthday -> age += 1
        -- arithmetic through a lens
      Younger  -> age %= max 0 . subtract 1

    view p =
      H.div_ []
        [ H.input_
            [ HP.value_ (p ^. name), HE.onInput Rename ]
        , H.p_ []
            [ text (p ^. name)
            , " is "
            , text (ms (p ^. age))
            , " years old"
            ]
        , H.button_ [ HE.onClick Birthday ] [ "birthday" ]
        , H.button_ [ HE.onClick Younger ] [ "younger" ]
        ]