Skip to content

Commit 6693cd4

Browse files
authored
Add a Thunk widget implementation (#22)
* Add a Thunk widget implementation * Add type signatures
1 parent 6470c51 commit 6693cd4

File tree

2 files changed

+140
-54
lines changed

2 files changed

+140
-54
lines changed

src/Halogen/VDom/Thunk.purs

Lines changed: 113 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
module Halogen.VDom.Thunk
2+
( Thunk
3+
, buildThunk
4+
, runThunk
5+
, hoist
6+
, thunked
7+
, thunk1
8+
, thunk2
9+
, thunk3
10+
) where
11+
12+
import Prelude
13+
14+
import Data.Function.Uncurried as Fn
15+
import Effect.Uncurried as EFn
16+
import Halogen.VDom as V
17+
import Halogen.VDom.Machine as M
18+
import Halogen.VDom.Util as Util
19+
import Unsafe.Coerce (unsafeCoerce)
20+
import Web.DOM.Node (Node)
21+
22+
foreign import data ThunkArgType
23+
24+
foreign import data ThunkIdType
25+
26+
data Thunk f i = Thunk ThunkId (Fn.Fn2 ThunkArg ThunkArg Boolean) (ThunkArg f i) ThunkArg
27+
28+
unsafeThunkId a. a ThunkId
29+
unsafeThunkId = unsafeCoerce
30+
31+
instance functorThunkFunctor f Functor (Thunk f) where
32+
map f (Thunk a b c d) = Thunk a b (c >>> map f) d
33+
34+
hoist f g. (f ~> g) Thunk f ~> Thunk g
35+
hoist k (Thunk a b c d) = Thunk a b (c >>> k) d
36+
37+
thunk a f i. Fn.Fn4 ThunkId (Fn.Fn2 a a Boolean) (a f i) a (Thunk f i)
38+
thunk = Fn.mkFn4 \tid eqFn f a →
39+
Thunk tid
40+
(unsafeCoerce eqFn Fn.Fn2 ThunkArg ThunkArg Boolean)
41+
(unsafeCoerce f ThunkArg f i)
42+
(unsafeCoerce a ThunkArg)
43+
44+
thunked a f i. (a a Boolean) (a f i) a Thunk f i
45+
thunked eqFn f =
46+
let
47+
tid = unsafeThunkId { f }
48+
eqFn' = Fn.mkFn2 eqFn
49+
in
50+
\a → Fn.runFn4 thunk tid eqFn' f a
51+
52+
thunk1 a f i. Fn.Fn2 (a f i) a (Thunk f i)
53+
thunk1 = Fn.mkFn2 \f a → Fn.runFn4 thunk (unsafeThunkId f) Util.refEq f a
54+
55+
thunk2 a b f i. Fn.Fn3 (a b f i) a b (Thunk f i)
56+
thunk2 =
57+
let
58+
eqFn = Fn.mkFn2 \a b →
59+
Fn.runFn2 Util.refEq a._1 b._1 &&
60+
Fn.runFn2 Util.refEq a._2 b._2
61+
in
62+
Fn.mkFn3 \f a b →
63+
Fn.runFn4 thunk (unsafeThunkId f) eqFn (\{ _1, _2 } → f _1 _2) { _1: a, _2: b }
64+
65+
thunk3 a b c f i. Fn.Fn4 (a b c f i) a b c (Thunk f i)
66+
thunk3 =
67+
let
68+
eqFn = Fn.mkFn2 \a b →
69+
Fn.runFn2 Util.refEq a._1 b._1 &&
70+
Fn.runFn2 Util.refEq a._2 b._2 &&
71+
Fn.runFn2 Util.refEq a._3 b._3
72+
in
73+
Fn.mkFn4 \f a b c →
74+
Fn.runFn4 thunk (unsafeThunkId f) eqFn (\{ _1, _2, _3 } → f _1 _2 _3) { _1: a, _2: b, _3: c }
75+
76+
runThunk f i. Thunk f i f i
77+
runThunk (Thunk _ _ render arg) = render arg
78+
79+
unsafeEqThunk f i. Fn.Fn2 (Thunk f i) (Thunk f i) Boolean
80+
unsafeEqThunk = Fn.mkFn2 \(Thunk a1 b1 _ d1) (Thunk a2 b2 _ d2) →
81+
Fn.runFn2 Util.refEq a1 a2 &&
82+
Fn.runFn2 Util.refEq b1 b2 &&
83+
Fn.runFn2 Util.refEq d1 d2
84+
85+
type ThunkState f i a w =
86+
{ thunk Thunk f i
87+
, vdom M.Step (V.VDom a w) Node
88+
}
89+
90+
buildThunk
91+
f i a w
92+
. (f i V.VDom a w)
93+
V.VDomSpec a w
94+
V.Machine (Thunk f i) Node
95+
buildThunk toVDom = renderThunk
96+
where
97+
renderThunk V.VDomSpec a w V.Machine (Thunk f i) Node
98+
renderThunk spec = EFn.mkEffectFn1 \t → do
99+
vdom ← EFn.runEffectFn1 (V.buildVDom spec) (toVDom (runThunk t))
100+
pure $ M.mkStep $ M.Step (M.extract vdom) { thunk: t, vdom } patchThunk haltThunk
101+
102+
patchThunk EFn.EffectFn2 (ThunkState f i a w) (Thunk f i) (V.Step (Thunk f i) Node)
103+
patchThunk = EFn.mkEffectFn2 \state t2 → do
104+
let { vdom: prev, thunk: t1 } = state
105+
if Fn.runFn2 unsafeEqThunk t1 t2
106+
then pure $ M.mkStep $ M.Step (M.extract prev) state patchThunk haltThunk
107+
else do
108+
vdom ← EFn.runEffectFn2 M.step prev (toVDom (runThunk t2))
109+
pure $ M.mkStep $ M.Step (M.extract vdom) { vdom, thunk: t2 } patchThunk haltThunk
110+
111+
haltThunk EFn.EffectFn1 (ThunkState f i a w) Unit
112+
haltThunk = EFn.mkEffectFn1 \state → do
113+
EFn.runEffectFn1 M.halt state.vdom

test/Main.purs

Lines changed: 27 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -2,19 +2,19 @@ module Test.Main where
22

33
import Prelude
44

5-
import Data.Exists (Exists, mkExists)
5+
import Data.Bifunctor (bimap)
66
import Data.Foldable (for_, traverse_)
77
import Data.Function.Uncurried as Fn
88
import Data.Maybe (Maybe(..), isNothing)
9-
import Data.Newtype (wrap)
9+
import Data.Newtype (class Newtype, un, wrap)
1010
import Data.Tuple (Tuple(..))
1111
import Effect (Effect)
1212
import Effect.Ref as Ref
1313
import Effect.Timer as Timer
1414
import Effect.Uncurried as EFn
1515
import Halogen.VDom as V
1616
import Halogen.VDom.DOM.Prop (Prop(..), propFromString, buildProp)
17-
import Halogen.VDom.Util (refEq)
17+
import Halogen.VDom.Thunk (Thunk, thunk1, buildThunk)
1818
import Unsafe.Coerce (unsafeCoerce)
1919
import Web.DOM.Document (Document) as DOM
2020
import Web.DOM.Element (toNode) as DOM
@@ -29,9 +29,12 @@ infixr 1 prop as :=
2929
prop a. String String Prop a
3030
prop key val = Property key (propFromString val)
3131

32-
type VDom = V.VDom (Array (Prop Void)) (Exists Thunk)
32+
newtype VDom a = VDom (V.VDom (Array (Prop a)) (Thunk VDom a))
3333

34-
data Thunk b = Thunk b (b VDom)
34+
instance functorHtmlFunctor VDom where
35+
map f (VDom vdom) = VDom (bimap (map (map f)) (map f) vdom)
36+
37+
derive instance newtypeVDomNewtype (VDom a) _
3538

3639
type State = Array Database
3740

@@ -55,19 +58,19 @@ type DBQuery =
5558
initialState State
5659
initialState = []
5760

58-
elem a w. String a Array (V.VDom a w) V.VDom a w
59-
elem n a = V.Elem Nothing (V.ElemName n) a
61+
elem a. String Array (Prop a) Array (VDom a) VDom a
62+
elem n a c = VDom $ V.Elem Nothing (V.ElemName n) a (unsafeCoerce c)
6063

61-
keyed a w. String a Array (Tuple String (V.VDom a w)) V.VDom a w
62-
keyed n a = V.Keyed Nothing (V.ElemName n) a
64+
keyed a. String Array (Prop a) Array (Tuple String (VDom a)) VDom a
65+
keyed n a c = VDom $ V.Keyed Nothing (V.ElemName n) a (unsafeCoerce c)
6366

64-
text a w. String V.VDom a w
65-
text = V.Text
67+
text a. String VDom a
68+
text a = VDom $ V.Text a
6669

67-
thunk a. (a VDom) a VDom
68-
thunk render val = V.Widget (mkExists (Thunk val render))
70+
thunk a b. (a VDom b) a VDom b
71+
thunk render val = VDom $ V.Widget $ Fn.runFn2 thunk1 render val
6972

70-
renderData State VDom
73+
renderData State VDom Void
7174
renderData st =
7275
elem "div" []
7376
[ elem "table"
@@ -108,41 +111,11 @@ renderData st =
108111
]
109112
]
110113

111-
type WidgetState a w =
112-
{ t :: Exists Thunk
113-
, step :: V.Step a w
114-
}
115-
116-
buildWidget
117-
V.VDomSpec (Array (Prop Void)) (Exists Thunk)
118-
V.Machine (Exists Thunk) DOM.Node
119-
buildWidget spec = render
120-
where
121-
render = EFn.mkEffectFn1 \t → case unsafeCoerce t of
122-
Thunk a render' → do
123-
step ← EFn.runEffectFn1 (V.buildVDom spec) (render' a)
124-
let state = { t, step }
125-
pure (V.mkStep (V.Step (V.extract step) state patch done))
126-
127-
patch = EFn.mkEffectFn2 \state t →
128-
case unsafeCoerce state.t, unsafeCoerce t of
129-
Thunk a render1, Thunk b render2 →
130-
if Fn.runFn2 refEq a b && Fn.runFn2 refEq render1 render2
131-
then
132-
pure (V.mkStep (V.Step (V.extract state.step) state patch done))
133-
else do
134-
step ← EFn.runEffectFn2 V.step state.step (render2 b)
135-
let nextState = { t, step }
136-
pure (V.mkStep (V.Step (V.extract step) nextState patch done))
137-
138-
done = EFn.mkEffectFn1 \state → do
139-
EFn.runEffectFn1 V.halt state.step
140-
141114
mkSpec
142115
DOM.Document
143-
V.VDomSpec (Array (Prop Void)) (Exists Thunk)
116+
V.VDomSpec (Array (Prop Void)) (Thunk VDom Void)
144117
mkSpec document = V.VDomSpec
145-
{ buildWidget
118+
{ buildWidget: buildThunk (un VDom)
146119
, buildAttributes: buildProp (const (pure unit))
147120
, document
148121
}
@@ -157,13 +130,13 @@ foreign import requestAnimationFrame ∷ Effect Unit → Effect Unit
157130

158131
mkRenderQueue
159132
a
160-
. V.VDomSpec (Array (Prop Void)) (Exists Thunk)
133+
. V.VDomSpec (Array (Prop Void)) (Thunk VDom Void)
161134
DOM.Node
162-
(a VDom)
135+
(a VDom Void)
163136
a
164137
Effect (a Effect Unit)
165138
mkRenderQueue spec parent render initialValue = do
166-
initMachine ← EFn.runEffectFn1 (V.buildVDom spec) (render initialValue)
139+
initMachine ← EFn.runEffectFn1 (V.buildVDom spec) (un VDom (render initialValue))
167140
_ ← DOM.appendChild (V.extract initMachine) parent
168141
ref ← Ref.new initMachine
169142
val ← Ref.new Nothing
@@ -173,24 +146,24 @@ mkRenderQueue spec parent render initialValue = do
173146
when (isNothing v) $ requestAnimationFrame do
174147
machine ← Ref.read ref
175148
Ref.read val >>= traverse_ \v' → do
176-
res ← EFn.runEffectFn2 V.step machine (render v')
149+
res ← EFn.runEffectFn2 V.step machine (un VDom (render v'))
177150
Ref.write res ref
178151
Ref.write Nothing val
179152

180153
mkRenderQueue'
181154
a
182-
. V.VDomSpec (Array (Prop Void)) (Exists Thunk)
155+
. V.VDomSpec (Array (Prop Void)) (Thunk VDom Void)
183156
DOM.Node
184-
(a VDom)
157+
(a VDom Void)
185158
a
186159
Effect (a Effect Unit)
187160
mkRenderQueue' spec parent render initialValue = do
188-
initMachine ← EFn.runEffectFn1 (V.buildVDom spec) (render initialValue)
161+
initMachine ← EFn.runEffectFn1 (V.buildVDom spec) (un VDom (render initialValue))
189162
_ ← DOM.appendChild (V.extract initMachine) parent
190163
ref ← Ref.new initMachine
191164
pure \v → do
192165
machine ← Ref.read ref
193-
res ← EFn.runEffectFn2 V.step machine (render v)
166+
res ← EFn.runEffectFn2 V.step machine (un VDom (render v))
194167
Ref.write res ref
195168

196169
main Effect Unit

0 commit comments

Comments
 (0)