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 -> 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
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)
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
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
widgetHold
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
...
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
Dynamic
s 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