Dave Laing
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
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:
The original idea had two criteria for implementations to meet:
The reflex library has:
The reflex library has:
The precise semantics are what makes the code and reasoning about the code composable
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
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
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 -> Intdata 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 -> Boolinstance Eq Person where
(==) (Person n1 a1) (Person n2 a2) =
n1 == n2 && a1 == a2instance Eq a => Eq (Maybe a) where
(==) (Just x1) (Just x2) =
x1 == x2
(==) Nothing Nothing =
True
(==) _ _ =
Falseclass Monoid m where
mempty :: m
(<>) :: m -> m -> minstance Monoid (Maybe a) where
mempty = Nothing
(<>) (Just x) _ = Just x
(<>) Nothing y = yclass Functor f where
fmap :: (a -> b) -> f a -> f binstance Functor Maybe where
fmap f (Just x) = Just (f x)
fmap _ Nothing = Nothingclass Applicative f where
pure :: a -> f a
(<*>) :: f (a -> b) -> f a -> f bWhat 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)
Eventdata Event a
data Event a
~
[(time, a)] eOutput = eInputinstance Functor Event where ...flipColour :: Colour -> Colour
flipColour Red = Blue
flipColour Blue = RedeOutput = flipColour <$> eInput(<$) :: Functor f => a -> f b -> f aeOutput = Blue <$ eInputffilter :: (a -> Bool)
-> Event a
-> Event aisRed :: Colour -> Bool
isRed Red = True
isRed Blue = FalseeOutput = ffilter isRed eInputeCount :: Event Intdiv3 :: Int -> Bool
div3 x = x `mod` 3 == 0div5 :: Int -> Bool
div5 x = x `mod` 5 == 0eFizz :: Event Text
eFizz = "Fizz" <$ ffilter div3 eCounteBuzz :: Event Text
eBuzz = "Buzz" <$ ffilter div5 eCounteFizz :: Event Text
eFizz = "Fizz" <$ ffilter div3 eCounteBuzz :: Event Text
eBuzz = "Buzz" <$ ffilter div5 eCountleftmost :: [Event a] -> Event aeLeft :: Event Text
eLeft = leftmost [eFizz, eBuzz]eInput = leftmost [Red <$ eRed, Blue <$ eBlue]mergeWith :: (a -> a -> a) -> [Event a] -> Event aeMerge :: Event Text
eMerge = mergeWith (<>) [eFizz, eBuzz]instance Monoid a => Monoid (Event a) where ...eMerge :: Event Text
eMerge = eFizz <> eBuzzeFizzBuzz :: 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 eSumBehaviordata Behavior a
data Behavior a
~
time -> ahold :: 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 asampleBlue eInput = do
bColour <- hold Blue eInput
sampleBlue eInput = do
bColour <- hold Blue eInput
tag sampleBlue eInput = do
bColour <- hold Blue eInput
tag bColoursampleBlue eInput eSample = do
bColour <- hold Blue eInput
tag bColoursampleBlue eInput eSample = do
bColour <- hold Blue eInput
tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [ ]
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [ eSample]
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [ eInput, eSample]
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [() <$ eInput, eSample]
return $ tag bColour eSamplesampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [() <$ eInput, eSample]
return $ tag bColour eAnysampleBlue eInput eSample = do
bColour <- hold Blue eInput
let eAny = leftmost [() <$ eInput, eSample]
return $ tag bColour eAnyattach ::
Behavior a
-> Event b
-> Event (a, b)attachWith :: (a -> b -> c)
-> Behavior a
-> Event b
-> Event cattachWithMaybe :: (a -> b -> Maybe c)
-> Behavior a
-> Event b
-> Event cgate ::
Behavior Bool
-> Event a
-> Event aDynamicdata Dynamic a
data Dynamic a
~
(Event a, Behavior a) updated :: Dynamic a
-> Event acurrent :: Dynamic a
-> Behavior aThis 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 dCountcounter :: ReflexM m
=> Dynamic Int
-> Event ()
-> Event ()
-> m (Dynamic Int)
counter eAdd eClear = do
dCount <- foldDyn ($) 0 . mergeWith (.) $ [
(+ 1) <$ eAdd
, const 0 <$ eClear
]
return dCountcounter :: ReflexM m
=> Dynamic Int
-> Event ()
-> Event ()
-> m (Dynamic Int)
counter dLimit eAdd eClear = do
dCount <- foldDyn ($) 0 . mergeWith (.) $ [
(+ 1) <$ eAdd
, const 0 <$ eClear
]
return dCountcounter :: 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 dCountcounter :: 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 dCountcounter :: 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 dCountcounter :: 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 dCountlimit :: 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 dLimitdata 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 dCountcounter :: 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 dCountcounter :: 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 dCountcounter :: 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 dCountcounter :: 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 dCountcounter :: 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 dCountcounter (Settings <$> dLimit <*> dStep) eAdd eClearLet's have a look at some higher order functions
map :: (a -> b)
-> [a]
-> [b]
foldr :: (a -> b -> b)
-> b
-> [a]
-> bLet's have a look at some higher order FRP
switch ::
Behavior (Event a)
-> Event aswitchPrompty :: 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)el :: ReflexM m
=> Text
-> m a
-> m atext :: 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 aelDynAttr :: ReflexM m
=> Text
-> Dynamic (Map Text Text)
-> m a
-> m aelClass :: ReflexM m
=> Text
-> Text
-> m a
-> m aelDynClass :: ReflexM m
=> Text
-> Dynamic Text
-> m a
-> m atodoItem :: ReflexM m
=> Text
-> m (Event ())
todoItem placeholder =
el "div" $ do
el "div" $
text placeholder
eRemove <- button "Remove"
return eRemovetodoItem :: ReflexM m
=> Text
-> m (Event ())
todoItem placeholder =
elClass "div" $ do
el "div" $
text placeholder
eRemove <- button "Remove"
return eRemovetodoItem :: ReflexM m
=> Text
-> m (Event ())
todoItem placeholder =
elClass "div" "todo-item" $ do
el "div" $
text placeholder
eRemove <- button "Remove"
return eRemovetodoItem :: ReflexM m
=> Text
-> m (Event ())
todoItem placeholder =
elClass "div" "todo-item" $ do
el "div" $
text placeholder
eRemove <- button "Remove"
dRemoveClass <- holdDyn "" $
"removed" <$ eRemove
return eRemovetodoItem :: 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 eRemovetodoItem :: 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 eRemovedata CheckboxConfig =
CheckboxConfig {
checkboxConfig_setValue :: Event Bool
, checkboxConfig_attributes :: Dynamic (Map Text Text)
}instance Default CheckboxConfig where ...checkbox :: (...)
=> Bool
-> CheckboxConfig
-> m Checkboxdata 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 eRemovetodoItem :: 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 eRemovetodoItem :: 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 eRemovetodoItem :: 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 eRemovetodoItem :: 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 TextInputdata 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 eDoneaddItem :: 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 eDoneaddItem :: 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 eDoneYou 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
widgetHoldlist
:: 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
...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
Dynamics or the nice collection management tools etcData61 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