Skip to content

Commit 22f3118

Browse files
author
Steve Krouse
committed
## DOM Recursion Problem Solved by Reflex
As I wrote in [The Model-View Recursion Problem in FRP](https://docs.google.com/document/d/1suHgeUvls6MZLHholN6r4KK0G5VaYArjKnALvmmJQhU/edit#): > In "Experience Report: Functional Reactive Programming and the DOM", authors describe a “recursion between FRP and the DOM” problem: “…the DOM tree in an FRP application can change in response to changes in FRP behaviors and events. However such new elements in the DOM tree may also produce new primitive event streams… which the FRP program needs to be able to react to. In other words, there is a cycle of dependencies…” > You start with two buttons. The first button doesn’t respond to clicks. But the second button, when clicked, adds two more buttons to the page, where again the first button doesn’t respond to clicks, but the second button can add more pairs of buttons on click. > ... I believe ... the solution is mutually recursive like the code in AFRP, and looks something like: > `clicksCount = count $ merge $ map (\[b1, b2] -> b1.clicks) nestedButtons` > `nestedButtons = repeat clicksCount [button, button]` > `buttons = flatten nestedButtons` Turns out the Reflex library allows me to neatly solve this problem. The core of the code looks like: ```haskell rec countedClicks <- foldDyn (\a b -> if evenButton a then b + 2 else b) 2 clicks clicks <- listListView (ffor countedClicks (\n -> [1..n])) (\ _ x -> dynTextButton x) ``` And it only took me ~5 hours to write those two lines. No joke. I use a few helper functions of my own creation. You can see the [full code here](/code/reflex/button_and_text.hs). And the [live version here](/code/reflex/button_and_text.jsexe/index.html). And that ~5 hours is not including the ~3 hours it took to get reflex set up on my chromebook. Cloud9, AWS, Digital Ocean, and Codeanywhere all didn't work for different reasons. Eventually I got Google App Engine to work but I didn't realize the version I used would delete everything I install over night. So I created a more permanent instance on Google Compute which works as well and lets me SSH in using the browser. I just have to remember to turn it off when I'm not using it (which I am pretty sure stops the billing), otherwise it costs $100 per month because I'm using a big box. I'm getting better at emacs which is fun. I install haskell syntax highlighting and am using the shell from within emacs to compile the code in a different window. And I have `sudo python -m SimpleHTTPSever 80` so I can see the code after I compile it with `ghcjs`. I can't use a lot of the really cool emacs haskell extensions because I'm using `ghcjs` instead of `ghc` and `./try-reflex` instead of `cabal` or `stack`, but I'm doing OK now. It took me a while to get used to all the fucking types in reflex, but I'm getting the hang of it. Next steps... I'm a little lost in terms of what's next because this problem, along with [Reflex ToDoMVC minus a few features](https://github.com/reflex-frp/reflex-todomvc/blob/develop/src/Reflex/TodoMVC.hs) seems to already be solved. One idea is that I can go through Reflex ToDo MVC, line by line, expression by expression, and type-annotate everything and add a bunch more comments. There are definitely parts of it I don't understand, such as the `def` keyword, `&`, and `.~`. In a vague sense, I'm working towards building a "CycleJS devtools dataflow diagram" experience for Reflex, so another step would be to draw pictures for each type, or a way that I'd display it on the screen and show the values flowing through. And another good step that comes to mind would be to figure out how to get ahold of Reflex internals so I can get a sense of the network and hook into the different event firings.
1 parent b5dd2b1 commit 22f3118

File tree

6 files changed

+176827
-0
lines changed

6 files changed

+176827
-0
lines changed

code/reflex/button_and_text.hs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
{-# LANGUAGE OverloadedStrings, RecursiveDo, ScopedTypeVariables, FlexibleContexts, TypeFamilies, ConstraintKinds #-}
2+
3+
import Prelude hiding (mapM, mapM_, all, sequence)
4+
5+
import Control.Monad hiding (mapM, mapM_, forM, forM_, sequence)
6+
import Control.Monad.Fix
7+
import Data.Map (Map)
8+
import qualified Data.Map as Map
9+
import Data.Foldable
10+
import Data.Monoid ((<>))
11+
import Data.Text (Text)
12+
import qualified Data.Text as T
13+
14+
import GHCJS.DOM.Types (JSM)
15+
16+
import Reflex
17+
import Reflex.Dom.Core
18+
import Data.Text.Encoding (encodeUtf8)
19+
20+
21+
main = mainWidget app
22+
23+
evenButton :: [Int] -> Bool
24+
evenButton [] = False
25+
evenButton [x] = even x
26+
27+
dynListToMap :: (Ord k, (Functor (Dynamic t))) => Dynamic t [k] -> Dynamic t (Map k k)
28+
dynListToMap l = ffor l (Map.fromList . (map $ \x -> (x, x)))
29+
30+
listListView :: (Ord k, (Functor (Dynamic t)), Adjustable t m, PostBuild t m, MonadFix m, MonadHold t m) => Dynamic t [k] -> (k -> Dynamic t k -> m (Event t a)) -> m (Event t [k])
31+
listListView l = do
32+
events <- listViewWithKey $ dynListToMap l
33+
return (fmap (fmap Map.keys) events)
34+
35+
dynTextButton :: (Adjustable t m, PostBuild t m, MonadFix m, MonadHold t m, DomBuilder t m, Show a) => Dynamic t a -> m (Event t ())
36+
dynTextButton dynString = do
37+
rec
38+
(el,_) <- el' "button" $ do
39+
dynText $ fmap (T.pack . show) dynString
40+
return (domEvent Click el)
41+
42+
app :: ( DomBuilder t m , DomBuilderSpace m ~ GhcjsDomSpace , MonadFix m , MonadHold t m, PostBuild t m) => m ()
43+
app = do
44+
45+
rec
46+
countedClicks <- foldDyn (\a b -> if evenButton a then b + 2 else b) 2 clicks
47+
48+
clicks <- listListView (ffor countedClicks (\n -> [1..n])) (\ _ x -> dynTextButton x)
49+
50+
51+
return ()
52+
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
<!DOCTYPE html>
2+
<html>
3+
<head>
4+
<script language="javascript" src="rts.js"></script>
5+
<script language="javascript" src="lib.js"></script>
6+
<script language="javascript" src="out.js"></script>
7+
</head>
8+
<body>
9+
</body>
10+
<script language="javascript" src="runmain.js" defer></script>
11+
</html>

0 commit comments

Comments
 (0)