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 writeMonadState operators (inside Effect)
l .= v -- set a field
l %= f -- modify a field
l += n -- increment a numeric field
l -= n -- decrement
l *= n -- multiplythis — 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 -= 1Generating 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 .= nGenerics
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, OverloadedLabels, 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 OverloadedLabelsHand-written
name :: Lens Person MisoString
name = lens _name $ \p n -> p { _name = n }Try it
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" ]
]