miso

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, 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 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" ]
        ]