Higher order FRP for front-end development

Dave Laing

data61

Virtual DOM

The DOM is a mutable tree with mutable state at the nodes and at the leaves

Working with the DOM directly can be slow

The virtual DOM is a data type that models the DOM, allow with efficient diff and patch operations

That helps with the mutable state

Several frameworks assume the DOM is stateless

This is not true, and causes problems

You have to capture the state and loop it back through your model

React.js can avoid this, with local state captured in components

Either way, you end up with a mutable tree of with mutable state at the nodes and at the leaves

It's just a different tree

Functional Reactive Programming

FRP is great for managing complex state that changes over time

You have first class values that encapsulate state that changes over time

Kind of like a souped-up, ultra-composable observer pattern in a library

Many things have claimed to be FRP libraries over the years

Few have matched the power of the original idea

The original idea had two types of things:

  • Events
  • Behaviors

The original idea had two criteria for implementations to meet:

  • Precise semantics
  • Support for continuous time

The reflex library has:

  • Events
  • Behaviors
  • Dynamics

The reflex library has:

  • Precise semantics
  • Support for discrete time

The precise semantics are what makes the code and reasoning about the code composable

Why Haskell?

The reflex library is written in Haskell, using GHCJS to compile to Javascript ...

... and reflex is the best demonstration of why FRP is great for front-end development

The sodium-typescript library is ready for use now ...

... but it doesn't currently have the bells and whistles we need

(I can also get more code on a slide with Haskell than with Typescript)

We're programmers, and languages are our tools just like libraries are ...

... but some of these tools can take a little getting used to

Just enough Haskell to be dangerous

An aside on types

The type systems feels nothing at all like the type systems from Java / C++

Types are simple tests and documentation that never go out of date

You mostly don't have to write them

They can be inferred most of the time

Show me the code

data Person =
  Person {
    name :: String
  , age :: Int
  }





  

data Person =
  Person {
    name :: String
  , age :: Int
  }

Person :: String -> Int -> Person



  

data Person =
  Person {
    name :: String
  , age :: Int
  }

Person :: String -> Int -> Person

name :: Person -> String

  

data Person =
  Person {
    name :: String
  , age :: Int
  }

Person :: String -> Int -> Person

name :: Person -> String

age :: Person -> Int

data Person =
  Person {
    name :: String
  , age :: Int
  }
happyBirthday :: Person -> Person
happyBirthday (Person name age) = 
  Person name (age + 1)
> happyBirthday (Person "alice" 31)
Person "alice" 32

data Maybe a =
    Just a 
  | Nothing

class Eq a where
  (==) :: a -> a -> Bool

instance Eq Person where
  (==) (Person n1 a1) (Person n2 a2) =
    n1 == n2 && a1 == a2

instance Eq a => Eq (Maybe a) where
  (==) (Just x1) (Just x2) =
    x1 == x2
  (==) Nothing Nothing =
    True
  (==) _ _ =
    False

class Monoid m where
  mempty :: m
  (<>)   :: m -> m -> m

instance Monoid (Maybe a) where
  mempty = Nothing
  (<>) (Just x) _ = Just x
  (<>) Nothing  y = y

class Functor f where
  fmap :: (a -> b) -> f a -> f b

instance Functor Maybe where
  fmap f (Just x) = Just (f x)
  fmap _ Nothing  = Nothing

class Applicative f where
  pure  :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b

What now?

loadName :: LoadDB String

loadAge :: LoadDB Int

loadPerson :: LoadDB Person
loadPerson = 
  Person <$> loadName <*> loadAge

  

loadName :: LoadDB String

loadAge :: LoadDB Int

loadPerson :: LoadDB Person
loadPerson = do
  name <- loadName
  age  <- loadAge
  return (Person name age)

parseName :: Parser String

parseAge :: Parser Int

parsePerson :: Parser Person
parsePerson = 
  Person <$> parseName <*> parseAge







  

parseName :: Parser String

parseAge :: Parser Int
  
parsePerson :: Parser Person
parsePerson = do
  parseHeader "Person"

  name <- parseName
  skipSpaces

  age  <- parseAge
  skipSpaces

  return (Person name age)

renderName :: String -> RenderHtml ()

renderAge :: Int -> RenderHtml ()

div :: RenderHtml a -> RenderHtml a

renderPerson :: Person -> RenderHtml ()
renderPerson (Person name age) =
  div ( do
    label "Name" name 
    label "Age" (show age)
  )

renderName :: String -> RenderHtml ()

renderAge :: Int -> RenderHtml ()

div :: RenderHtml a -> RenderHtml a

renderPerson :: Person -> RenderHtml ()
renderPerson (Person name age) =
  div $ do
    label "Name" name 
    label "Age" (show age)
  

Event

data Event a



 

data Event a

  ~

[(time, a)] 

eOutput = eInput

instance Functor Event where ...

flipColour :: Colour -> Colour
flipColour Red  = Blue
flipColour Blue = Red
eOutput = flipColour <$> eInput

(<$) :: Functor f => a -> f b -> f a

eOutput = Blue <$ eInput

ffilter :: (a -> Bool) 
        -> Event a 
        -> Event a

isRed :: Colour -> Bool
isRed Red  = True
isRed Blue = False
eOutput = ffilter isRed eInput

eCount :: Event Int

div3 :: Int -> Bool
div3 x = x `mod` 3 == 0
div5 :: Int -> Bool
div5 x = x `mod` 5 == 0

eFizz :: Event Text
eFizz = "Fizz" <$ ffilter div3 eCount
eBuzz :: Event Text
eBuzz = "Buzz" <$ ffilter div5 eCount

eFizz :: Event Text
eFizz = "Fizz" <$ ffilter div3 eCount
eBuzz :: Event Text
eBuzz = "Buzz" <$ ffilter div5 eCount

leftmost :: [Event a] -> Event a

eLeft :: Event Text
eLeft = leftmost [eFizz, eBuzz]

eInput = leftmost [Red <$ eRed, Blue <$ eBlue]

mergeWith :: (a -> a -> a) -> [Event a] -> Event a

eMerge :: Event Text
eMerge = mergeWith (<>) [eFizz, eBuzz]

instance Monoid a => Monoid (Event a) where ...

eMerge :: Event Text
eMerge = eFizz <> eBuzz

eFizzBuzz :: Event Text
eFizzBuzz = leftmost [eFizz <> eBuzz, eCount]

var button = document.getElementById("rx-button");
var clicks = Rx.Observable.fromEvent(button, "click");

var ones = clicks.scan(count => count + 1, 0);

var hundreds = ones.map(function(x) { return x * 100; });

var sum = ones.combineLatest(hundreds, function(o, h) {
    return o + h; 
  });

sum.subscribe(function(s) { alert(s); });

  button <- getElementById "reflex-button"
  eClick <- domEvent Click button

  eOnes <- accum (+) 0 (1 <$ eClick)

  let eHundreds = (* 100) <$> eOnes

  let eSum = mergeWith (+) [eOnes, eHundreds]

  alertEvent show eSum

Behavior

data Behavior a



 

data Behavior a

  ~

time -> a

hold :: ReflexM m 
     =>             a
     ->       Event a
     -> m (Behavior a)

sampleBlue                = do
  bColour <- hold
  

sampleBlue                = do
  bColour <- hold Blue
  

sampleBlue eInput         = do
  bColour <- hold Blue
 

sampleBlue eInput         = do
  bColour <- hold Blue eInput
 

tag :: Behavior a
    -> Event    b
    -> Event    a

sampleBlue eInput         = do
  bColour <- hold Blue eInput
 

sampleBlue eInput         = do
  bColour <- hold Blue eInput
           tag 

sampleBlue eInput         = do
  bColour <- hold Blue eInput
           tag bColour

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
           tag bColour

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
           tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput

  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [                     ]
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [              eSample]
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [      eInput, eSample]
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [() <$ eInput, eSample]
  return $ tag bColour eSample

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [() <$ eInput, eSample]
  return $ tag bColour eAny

sampleBlue eInput eSample = do
  bColour <- hold Blue eInput
  let eAny = leftmost [() <$ eInput, eSample]
  return $ tag bColour eAny

attach          :: 
                   Behavior a 
                -> Event b 
                -> Event (a, b)

attachWith      :: (a -> b -> c) 
                -> Behavior a
                -> Event b
                -> Event c

attachWithMaybe :: (a -> b -> Maybe c) 
                -> Behavior a
                -> Event b
                -> Event c

gate            :: 
                   Behavior Bool
                -> Event a
                -> Event a

Dynamic

data Dynamic a



 

data Dynamic a

  ~ 

(Event a, Behavior a) 

updated :: Dynamic a 
        -> Event a

current :: Dynamic a 
        -> Behavior a

This is what lets reflex catch up to the virtual DOM

hold        :: ReflexM m
            => 
               a 
            -> Event a 
            -> m (Behavior a)

holdDyn     :: ReflexM m
            => 
               a 
            -> Event a 
            -> m (Dynamic  a)

foldDyn     :: ReflexM m
            => (a -> b -> b) 
            -> b 
            -> Event a 
            -> m (Dynamic  b)

foldDyn ($) :: ReflexM m
            => (a -> b -> b) 
            -> b 
            -> Event a 
            -> m (Dynamic  b)

foldDyn ($) :: ReflexM m
            => 
               c 
            -> Event (c -> c)
            -> m (Dynamic  c)

counter :: ReflexM m
        =>

           m (Dynamic Int)
counter             =



 

counter :: ReflexM m
        => Event ()

        -> m (Dynamic Int)
counter             =



 

counter :: ReflexM m
        => Event ()

        -> m (Dynamic Int)
counter eAdd        =



 

counter :: ReflexM m
        => Event ()

        -> m (Dynamic Int)
counter eAdd        =
  foldDyn ($) 0
  
  
   

counter :: ReflexM m
        => Event ()

        -> m (Dynamic Int)
counter eAdd        =
  foldDyn ($) 0                 $
      (+ 1)   <$ eAdd
  
   

counter :: ReflexM m
        => Event ()
        -> Event ()
        -> m (Dynamic Int)
counter eAdd        =
  foldDyn ($) 0                 $
      (+ 1)   <$ eAdd
  
   

counter :: ReflexM m
        => Event ()
        -> Event ()
        -> m (Dynamic Int)
counter eAdd eClear =
  foldDyn ($) 0                 $
      (+ 1)   <$ eAdd
  
   

counter :: ReflexM m
        => Event ()
        -> Event ()
        -> m (Dynamic Int)
counter eAdd eClear =
  foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
  
    ] 

counter :: ReflexM m
        => Event ()
        -> Event ()
        -> m (Dynamic Int)
counter eAdd eClear =
  foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]

counter :: ReflexM m
        => Event ()
        -> Event ()
        -> m (Dynamic Int)
counter eAdd eClear =
  foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]

counter :: ReflexM m
        => 
           Event ()
        -> Event ()
        -> m (Dynamic Int)
counter        eAdd eClear = do



  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter        eAdd eClear = do



  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit eAdd eClear = do



  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit eAdd eClear = do
  let dLimitOK = (<) <$> dCount <*> dLimit


  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit eAdd eClear = do
  let dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAdd
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit eAdd eClear = do
  let dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit eAdd eClear = do
  let dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$ eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

limit :: ReflexM m
      => Event () 
      -> Event ()
      -> Event ()
      -> m (Dynamic Int)
limit eStart eAdd eClear = do
  eLoadLimit <- performEvent (loadLimitDb <$ eStart)

  dLimit <- foldDyn ($) 5 . mergeWith (.) $ [
      const   <$> eLoadLimit
    , (+ 1)   <$  eAdd
    , const 0 <$  eClear
    ]
    
  performEvent_ (saveLimitDb <$> update dLimit) 
    
  return dLimit

data Settings =
  Settings {
    settingLimit :: Int
  , settingStep  :: Int
  }

counter :: ReflexM m
        => Dynamic Int
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit    eAdd eClear = do
  let 
      
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$                      eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Settings
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dLimit    eAdd eClear = do
  let 
      
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$                      eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Settings
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dSettings eAdd eClear = do
  let 
      
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$                      eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Settings
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dSettings eAdd eClear = do
  let dLimit   = settingsLimit <$> dSettings
      
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$                      eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Settings
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dSettings eAdd eClear = do
  let dLimit   = settingsLimit <$> dSettings
      dStep    = settingsStep  <$> dSettings
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+ 1)   <$                      eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter :: ReflexM m
        => Dynamic Settings
        -> Event ()
        -> Event ()
        -> m (Dynamic Int)
counter dSettings eAdd eClear = do
  let dLimit   = settingsLimit <$> dSettings
      dStep    = settingsStep  <$> dSettings
      dLimitOK = (<) <$> dCount <*> dLimit
      eAddOK   = gate (current dLimitOK) eAdd

  dCount <- foldDyn ($) 0 . mergeWith (.) $ [
      (+  )   <$> tag (current dStep) eAddOK
    , const 0 <$ eClear
    ]
    
  return dCount

counter (Settings <$> dLimit <*> dStep) eAdd eClear

Higher order FRP

Let's have a look at some higher order functions

map   :: (a -> b) 
      -> [a] 
      -> [b]
  

foldr :: (a -> b -> b) 
      -> b 
      -> [a] 
      -> b

Let's have a look at some higher order FRP

switch        :: 
               
                 Behavior (Event a)
              ->           Event a

switchPrompty :: ReflexM m 
              =>           Event a
              -> Event    (Event a)
              -> m        (Event a)

switchColour :: ReflexM m
             =>


             -> m (Event Colour, Event Colour)
switchColour                          = do









    



  

switchColour :: ReflexM m
             => Event ()


             -> m (Event Colour, Event Colour)
switchColour                          = do









    



  

switchColour :: ReflexM m
             => Event ()


             -> m (Event Colour, Event Colour)
switchColour eSwitch1                 = do









    



  

switchColour :: ReflexM m
             => Event ()
             -> Event ()

             -> m (Event Colour, Event Colour)
switchColour eSwitch1                 = do









    



  

switchColour :: ReflexM m
             => Event ()
             -> Event ()

             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2        = do









    



  

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2        = do









    



  

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do









    



  

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do





  let eOut1 = _





  let eOut2 = _

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- _



  let eOut1 = _

  bOut2 <- _



  let eOut2 = _

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- _



  let eOut1 = switch bOut1

  bOut2 <- _



  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold _      _



  let eOut1 = switch bOut1

  bOut2 <- hold _     _



  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput _



  let eOut1 = switch bOut1

  bOut2 <- hold _     _



  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput _



  let eOut1 = switch bOut1

  bOut2 <- hold never _



  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput . leftmost $ [
      _      <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold never . leftmost $ [
      _      <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput . leftmost $ [
      eInput <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold never . leftmost $ [
      _      <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput . leftmost $ [
      eInput <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold never . leftmost $ [
      never  <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold never . leftmost $ [
      never  <$ eSwitch1
    , _      <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event ()
             -> Event ()
             -> Event Colour
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- hold           eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold           never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  bOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold           never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  eOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]
  let eOut1 = switch bOut1

  bOut2 <- hold           never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  eOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]


  bOut2 <- hold           never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  eOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]


  bOut2 <- switchPromptly never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  eOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]


  eOut2 <- switchPromptly never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]
  let eOut2 = switch bOut2

  return (eOut1, eOut2)

switchColour :: ReflexM m
             => Event () 
             -> Event () 
             -> Event Colour 
             -> m (Event Colour, Event Colour)
switchColour eSwitch1 eSwitch2 eInput = do

  eOut1 <- switchPromptly eInput . leftmost $ [
      eInput <$ eSwitch1
    , never  <$ eSwitch2
    ]


  eOut2 <- switchPromptly never . leftmost $ [
      never  <$ eSwitch1
    , eInput <$ eSwitch2
    ]


  return (eOut1, eOut2)

Working with the DOM

el :: ReflexM m
   => Text 
   -> m a 
   -> m a
text :: ReflexM m
     => Text 
     -> m ()

el "div" $
  text "TODO"

button :: ReflexM m
       => Text 
       -> m (Event ())

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem label =
  el "div" $ do
    el "div" $ 
      text label
    button "Remove"

dynText :: ReflexM m
        => Dynamic Text 
        -> m ()

el "div" $ do



  eRemove <- todoItem "TODO"




  return ()

el "div" $ do



  eRemove <- todoItem "TODO"

  dLabel <- holdDyn "" $ 
    "Removed:" <$ eRemove

  return ()

el "div" $ do
  el "div" $ 
    dynText dLabel

  eRemove <- todoItem "TODO"

  dLabel <- holdDyn "" $ 
    "Removed:" <$ eRemove

  return ()

el "div" $ do
  el "div" $ 
    dynText dLabel

  eRemove <- todoItem "TODO"

  dLabel <- holdDyn "" $ 
    "Removed:" <$ eRemove

  return ()

elAttr    :: ReflexM m
          => Text 
          ->          Map Text Text 
          -> m a 
          -> m a

elDynAttr :: ReflexM m
          => Text 
          -> Dynamic (Map Text Text) 
          -> m a 
          -> m a

elClass    :: ReflexM m
           => Text 
           ->         Text 
           -> m a 
           -> m a

elDynClass :: ReflexM m
           => Text 
           -> Dynamic Text 
           -> m a 
           -> m a

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  el      "div"             $ do

    el         "div"              $
      text placeholder

    eRemove <- button "Remove"




    return eRemove

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  elClass "div"             $ do

    el         "div"              $
      text placeholder

    eRemove <- button "Remove"




    return eRemove

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  elClass "div" "todo-item" $ do

    el         "div"              $
      text placeholder

    eRemove <- button "Remove"




    return eRemove

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  elClass "div" "todo-item" $ do

    el         "div"              $
      text placeholder

    eRemove <- button "Remove"

    dRemoveClass <- holdDyn "" $
      "removed" <$ eRemove

    return eRemove

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  elClass "div" "todo-item" $ do

    elDynClass "div" dRemoveClass $
      text placeholder

    eRemove <- button "Remove"

    dRemoveClass <- holdDyn "" $
      "removed" <$ eRemove

    return eRemove

todoItem :: ReflexM m
         => Text 
         -> m (Event ())
todoItem placeholder =
  elClass "div" "todo-item" $ do

    elDynClass "div" dRemoveClass $
      text placeholder

    eRemove <- button "Remove"

    dRemoveClass <- holdDyn "" $
      "removed" <$ eRemove

    return eRemove

data CheckboxConfig = 
  CheckboxConfig { 
      checkboxConfig_setValue   :: Event   Bool
    , checkboxConfig_attributes :: Dynamic (Map Text Text)
    }
instance Default CheckboxConfig where ...

checkbox :: (...) 
         => Bool 
         -> CheckboxConfig
         -> m Checkbox

data Checkbox = 
  Checkbox { 
     checkbox_value  :: Dynamic Bool
   , checkbox_change :: Event   Bool
   }

data TodoItemConfig =
  TodoItemConfig {
    todoItemConfig_dText :: Dynamic Text
  }

data TodoItem =
  TodoItem {
    todoItem_dComplete :: Dynamic Bool
  , todoItem_eRemove   :: Event   ()
  }

todoItem :: ReflexM m
         => TodoItemConfig 
         -> m TodoItem
todoItem (TodoItemConfig dText) =
  elClass "div" "todo-item" $ do
  
  
  

    elDynClass "div" dRemoveClass $
      dynText dText

    eRemove <- button "Remove"
    dRemoveClass <- holdDyn "" $ 
      "removed" <$ eRemove

    return $ 
      TodoItem           eRemove

todoItem :: ReflexM m
         => TodoItemConfig 
         -> m TodoItem
todoItem (TodoItemConfig dText) =
  elClass "div" "todo-item" $ do
    cb <- checkbox False def
  
  

    elDynClass "div" dRemoveClass $
      dynText dText

    eRemove <- button "Remove"
    dRemoveClass <- holdDyn "" $ 
      "removed" <$ eRemove

    return $ 
      TodoItem           eRemove

todoItem :: ReflexM m
         => TodoItemConfig 
         -> m TodoItem
todoItem (TodoItemConfig dText) =
  elClass "div" "todo-item" $ do
    cb <- checkbox False def
    let
      dComplete = cb ^. checkbox_value

    elDynClass "div" dRemoveClass $
      dynText dText

    eRemove <- button "Remove"
    dRemoveClass <- holdDyn "" $ 
      "removed" <$ eRemove

    return $ 
      TodoItem           eRemove

todoItem :: ReflexM m
         => TodoItemConfig 
         -> m TodoItem
todoItem (TodoItemConfig dText) =
  elClass "div" "todo-item" $ do
    cb <- checkbox False def
    let
      dComplete = cb ^. checkbox_value

    elDynClass "div" dRemoveClass $
      dynText dText

    eRemove <- button "Remove"
    dRemoveClass <- holdDyn "" $ 
      "removed" <$ eRemove

    return $ 
      TodoItem dComplete eRemove

todoItem :: ReflexM m
         => TodoItemConfig 
         -> m TodoItem
todoItem (TodoItemConfig dText) =
  elClass "div" "todo-item" $ do
    cb <- checkbox False def
    let
      dComplete = cb ^. checkbox_value

    elDynClass "div" dRemoveClass $
      dynText dText

    eRemove <- button "Remove"
    dRemoveClass <- holdDyn "" $ 
      "removed" <$ eRemove

    return $ 
      TodoItem dComplete eRemove

  ...
    let
      dComplete = cb ^. checkbox_value






    elDynClass "div"                    dRemoveClass  $ 
      dynText dText
  ...

  ...
    let
      dComplete = cb ^. checkbox_value

      mkCompleteClass False = ""
      mkCompleteClass True  = "completed "



    elDynClass "div"                    dRemoveClass  $ 
      dynText dText
  ...

  ...
    let
      dComplete = cb ^. checkbox_value

      mkCompleteClass False = ""
      mkCompleteClass True  = "completed "

      dCompleteClass = mkCompleteClass <$> dComplete

    elDynClass "div"                    dRemoveClass  $ 
      dynText dText
  ...

  ...
    let
      dComplete = cb ^. checkbox_value

      mkCompleteClass False = ""
      mkCompleteClass True  = "completed "

      dCompleteClass = mkCompleteClass <$> dComplete

    elDynClass "div" (dCompleteClass <> dRemoveClass) $ 
      dynText dText
  ...

  ...
    let
      dComplete = cb ^. checkbox_value

      mkCompleteClass False = ""
      mkCompleteClass True  = "completed "

      dCompleteClass = mkCompleteClass <$> dComplete

    elDynClass "div" (dCompleteClass <> dRemoveClass) $ 
      dynText dText
  ...

data TextInputConfig = 
  TextInputConfig { 
      textInputConfig_inputType    :: Text
    , textInputConfig_initialValue :: Text
    , textInputConfig_setValue     :: Event  Text
    , textInputConfig_attributes   :: 
        Dynamic (Map Text Text)
    }
instance Default TextInputConfig where ...

textInput :: ReflexM m
          => TextInputConfig
          -> m TextInput

data TextInput = 
  TextInput { 
      textInput_value          :: Dynamic Text
    , textInput_input          :: Event Text
    , textInput_keypress       :: Event Word
    , textInput_keydown        :: Event Word
    , textInput_keyup          :: Event Word
    , textInput_hasFocus       :: Dynamic Bool
    , textInput_builderElement :: 
        InputElement EventResult GhcjsDomSpace
    }

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def                                                      









  

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")








  

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")



  let
    bValue   = current $ ti ^. textInput_value



  

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")



  let
    bValue   = current $ ti ^. textInput_value
    eAtEnter = tag bValue (getKey ti Enter)


  

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")



  let
    bValue   = current $ ti ^. textInput_value
    eAtEnter = tag bValue (getKey ti Enter)
    eDone    = ffilter (not . Text.null) eAtEnter

  

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")



  let
    bValue   = current $ ti ^. textInput_value
    eAtEnter = tag bValue (getKey ti Enter)
    eDone    = ffilter (not . Text.null) eAtEnter

  return eDone

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")
        & textInputConfig_setValue .~
            ("" <$ eDone)

  let
    bValue   = current $ ti ^. textInput_value
    eAtEnter = tag bValue (getKey ti Enter)
    eDone    = ffilter (not . Text.null) eAtEnter

  return eDone

addItem :: ReflexM m 
        => m (Event Text)
addItem = do
  ti <- textInput $
    def & textInputConfig_attributes .~
            pure ("placeholder" =: "What shall we do today?")
        & textInputConfig_setValue .~
            ("" <$ eDone)

  let
    bValue   = current $ ti ^. textInput_value
    eAtEnter = tag bValue (getKey ti Enter)
    eDone    = ffilter (not . Text.null) eAtEnter

  return eDone

Switching widgets

You often have a choice between hiding widgets or switching them

textWidget   :: ReflexM m 
             => m (Event Text)
buttonWidget :: ReflexM m 
             => m (Event Text)
tickWidget   :: ReflexM m 
             => m (Event Text)

  eSwitch <- el "div" $
    button "Switch"

  dToggle <- toggle True eSwitch

  let 
    dNotToggle = not <$> dToggle

  let
    mkHidden False = "hide"
    mkHidden True  = ""

    dHide1 = mkHidden <$> dToggle
    dHide2 = mkHidden <$> dNotToggle












  

  let
    mkHidden False = "hide"
    mkHidden True  = ""

    dHide1 = mkHidden <$> dToggle
    dHide2 = mkHidden <$> dNotToggle

  eText1 <- elDynClass "div" dHide1 $
    textWidget

  eText2 <- elDynClass "div" dHide2 $
    buttonWidget






  

  let
    mkHidden False = "hide"
    mkHidden True  = ""

    dHide1 = mkHidden <$> dToggle
    dHide2 = mkHidden <$> dNotToggle

  eText1 <- elDynClass "div" dHide1 $
    textWidget

  eText2 <- elDynClass "div" dHide2 $
    buttonWidget

  let 
    eText = leftmost [
        gate (current dToggle   ) eText1
      , gate (current dNotToggle) eText2

      ]

  let
    mkHidden False = "hide"
    mkHidden True  = ""

    dHide1 = mkHidden <$> dToggle
    dHide2 = mkHidden <$> dNotToggle

  eText1 <- elDynClass "div" dHide1 $
    textWidget

  eText2 <- elDynClass "div" dHide2 $
    buttonWidget

  let 
    eText = leftmost [
        gate (current dToggle   ) eText1
      , gate (current dNotToggle) eText2
      , "" <$ eSwitch
      ]

widgetHold :: ReflexM m
           =>         m          a 
           -> Event  (m          a) 
           ->         m (Dynamic a)

  let
    eToggle = updated     dToggle
    eShow1  = ffilter id  eToggle
    eShow2  = ffilter not eToggle










  

  let
    eToggle = updated     dToggle
    eShow1  = ffilter id  eToggle
    eShow2  = ffilter not eToggle

  deText <- widgetHold textWidget . leftmost $ [
      textWidget   <$ eShow1
    , buttonWidget <$ eShow2
    ]





  

  let
    eToggle = updated     dToggle
    eShow1  = ffilter id  eToggle
    eShow2  = ffilter not eToggle

  deText <- widgetHold textWidget . leftmost $ [
      textWidget   <$ eShow1
    , buttonWidget <$ eShow2
    ]

  let
    eText = 
        switch (current deText)
        
  

  let
    eToggle = updated     dToggle
    eShow1  = ffilter id  eToggle
    eShow2  = ffilter not eToggle

  deText <- widgetHold textWidget . leftmost $ [
      textWidget   <$ eShow1
    , buttonWidget <$ eShow2
    ]

  let
    eText = leftmost [
        switch (current deText)
      , "" <$ eSwitch
      ]

It becomes clearer why you would want this when we replace buttonWidget with tickWidget

With hiding

With widgetHold

Collections

list 
  ::    Dynamic (Map k v       )
  ->   (Dynamic        v -> m a) 
  -> m (Dynamic (Map k        a))

There are going to be some common elements in how we approach this

  elClass "div" "todo-list" $ do



















  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

















  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

    -- dModel :: Dynamic (Map Int ?)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves -- ?
      ]











  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

    -- dModel :: Dynamic (Map Int ?)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves -- ?
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv -- ?








  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

    -- dModel :: Dynamic (Map Int ?)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves -- ?
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv -- ?

    let
      dAllComplete = fmap and dmCompletes -- ?
      dAnyComplete = fmap or  dmCompletes -- ?




  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

    -- dModel :: Dynamic (Map Int ?)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves -- ?
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv -- ?

    let
      dAllComplete = fmap and dmCompletes -- ?
      dAnyComplete = fmap or  dmCompletes -- ?

    eMarkAllComplete <- markAllComplete dAllComplete
    eClearComplete   <- clearComplete   dAnyComplete

  

  elClass "div" "todo-list" $ do
    eAdd   <- addItem
    dCount <- count eAdd

    -- dModel :: Dynamic (Map Int ?)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves -- ?
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv -- ?

    let
      dAllComplete = fmap and dmCompletes -- ?
      dAnyComplete = fmap or  dmCompletes -- ?

    eMarkAllComplete <- markAllComplete dAllComplete
    eClearComplete   <- clearComplete   dAnyComplete

    return ()

There are two ways we can fill in the details

Option 1: Model all the things

data TodoItem =
  TodoItem {
    itemComplete :: Bool
  , itemText     :: Text
  }

todoItem :: ReflexM m 
         => Event Bool 
         -> Event () 
         -> Dynamic TodoItem 
         -> m (Event (TodoItem -> TodoItem), Event ())

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)









     
     
     
     
      
      
      
    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd'
        
        
      ]




     
     
     
     
      
      
      
    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd'
        
        
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

     
     
     
     
      
      
      
    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd'
        
        
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      eChanges = combineEvents fst dmList
      eRemoves = combineEvents snd dmList
      
      
      
      
    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd'
      , updateKeys <$> eChanges
      , removeKeys <$> eRemoves
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      eChanges = combineEvents fst dmList
      eRemoves = combineEvents snd dmList
      
      
      
      
    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int TodoItem)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd'
      , updateKeys <$> eChanges
      , removeKeys <$> eRemoves
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      eChanges = combineEvents fst dmList
      eRemoves = combineEvents snd dmList

      dmCompletes  = fmap itemComplete dModel
      dAllComplete = fmap and          dmCompletes
      dAnyComplete = fmap or           dmCompletes
    ...

That's fine if something else in our program needs access to the model

If that's not the case, we're exposing and tracking a lot of state that we're not going to use

Option 2: internalize as much state as we can

todoItem :: ReflexM m 
         => Event Bool 
         -> Event () 
         -> Dynamic Text 
         -> m (Dynamic Bool, Event ())

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)














    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd

      ]










    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd

      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv







    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd

      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      dmCompletes = combineDynamic fst dmList
      eRemoves    = combineEvent   snd dmList



    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      dmCompletes = combineDynamic fst dmList
      eRemoves    = combineEvent   snd dmList



    ...

todoList :: ReflexM m 
         => m ()
todoList = 
    ... -- dModel :: Dynamic (Map Int Text)
    dModel <- foldDyn ($) Map.empty . mergeWith (.) $ [
        attachWith Map.insert (current dCount) eAdd
      , removeKeys <$> eRemoves
      ]

    dmList <- el "ul" . list dModel $ \dv ->
      todoItem eMarkAllComplete eClearComplete dv

    let
      dmCompletes = combineDynamic fst dmList
      eRemoves    = combineEvent   snd dmList

      dAllComplete = fmap and dmCompletes
      dAnyComplete = fmap or  dmCompletes
    ...

Conclusion

Hopefully that gave you a bit of a taste of a flavour of FRP that is a bit closer to the original idea than you might have come across before

The sodium library is worth looking at as well

  • has support for a lot of languages, including typescript
  • Manning has a book on it: "Functional Reactive Programming"
  • doesn't currently have Dynamics or the nice collection management tools etc

Data61 do free 3 day courses on Haskell if you're keen to learn more about that

I'm happy to answer questions or help out with reflex or with sodium, so track me down if you're interested in this stuff