{-# LANGUAGE RankNTypes #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE TupleSections #-} {-# LANGUAGE OverloadedStrings #-} module Main where -- This example demonstrates how you can split your update function -- into separate update functions for parts of your model and then -- combine them into a single update function operating on the whole -- model which combines their effects. import Control.Monad import Data.Monoid import Miso import Miso.Lens import Miso.String -- In this slightly contrived example, our model consists of two -- counters. When one of those counters is incremented, the other is -- decremented and the other way around. type Model = (Int, Int) data Action = Increment | Decrement | NoOp deriving (Show, Eq) -- We are going to use 'Lens'es in this example. Since @miso@ does not -- depend on a lens library we are going to define a couple of -- utilities ourselves. We recommend that in your own applications, -- you depend on a lens library such as @lens@ or @microlens@ to get -- these definitions. -- | You can find this under the same name in @lens@ and -- @microlens@. @lens@ also provides the infix operator '%%~' as a -- synonym for 'traverseOf'. -- -- In this example we are only going to use this when applied to -- 'Lens' m a' and using 'Effect Action' for the @f@ type variable. In -- that case the specialized type signature is: -- -- @traverseOf :: Functor f => Lens' m a -> (a -> Effect Action a) -> s -> Effect action s traverseOf :: Functor f => Lens s t a b -> (a -> f b) -> s -> f t traverseOf = id -- | A lens into the first element of a tuple. Both @lens@ and -- @microlens@ provide this under the same name. _1 :: Lens (a,c) (b,c) a b _1 f (a,c) = (,c) <$> f a -- | A lens into the second element of a tuple. Both @lens@ and -- @microlens@ provide this under the same name. _2 :: Lens (c,a) (c,b) a b _2 f (c,a) = (c,) <$> f a -- | Update function for the first counter in our 'Model'. updateFirstCounter :: Action -> Int -> Effect Action Int updateFirstCounter Increment m = noEff (m + 1) updateFirstCounter Decrement m = noEff (m - 1) updateFirstCounter NoOp m = noEff m -- | Update function for the second counter in our 'Model'. As we’ve -- mentioned before, this counter is decremented when the first -- counter is incremented and the other way around. updateSecondCounter :: Action -> Int -> Effect Action Int updateSecondCounter Increment m = noEff (m - 1) updateSecondCounter Decrement m = noEff (m + 1) updateSecondCounter NoOp m = noEff m -- | This is the combined update function for both counters. updateModel :: Action -> Model -> Effect Action Model updateModel act = let -- We use 'traverseOf' to lift an update function for one -- counter to an update function that operates on both -- counters. The lifted function leaves the other counter -- untouched. liftedUpdateFirst :: Model -> Effect Action Model liftedUpdateFirst = traverseOf _1 (updateFirstCounter act) liftedUpdateSecond :: Model -> Effect Action Model liftedUpdateSecond = traverseOf _2 (updateSecondCounter act) in -- Since 'Effect Action' is an instance of 'Monad', we can just -- use '<=<' to compose these lifted update functions. It might -- be helpful to look at the type signature of '<=<' specialized -- for 'Effect Action': -- -- @(<=<) :: (b -> Effect Action c) -> (a -> Effect Action b) -> a -> Effect Action c liftedUpdateFirst <=< liftedUpdateSecond main :: IO () main = startApp App { initialAction = NoOp, ..} where model = (0, 0) update = updateModel view = viewModel events = defaultEvents subs = [] mountPoint = Nothing viewModel :: Model -> View Action viewModel (x, y) = div_ [] [ button_ [onClick Increment] [text "+"] , text (ms (show x) <> " | " <> ms (show y)) , button_ [onClick Decrement] [text "-"] ]