-
Notifications
You must be signed in to change notification settings - Fork 0
/
Main.hs
270 lines (219 loc) · 8.78 KB
/
Main.hs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
{-# LANGUAGE TypeFamilies, DeriveDataTypeable, FlexibleContexts, GADTs #-}
module Main (
main
) where
import Data.Typeable
import qualified Data.Traversable as Traversable
import Control.Applicative
import Control.Monad
import Data.Maybe
import Data.IORef
import System.Mem.Weak
--transformers package:
import Control.Monad.IO.Class
main = example
data Person = Person {
name :: String
} deriving (Show, Typeable)
data Company = Company {
legalName :: String
} deriving (Show, Typeable)
-- the only thing we need MonadIO for in this exmple is printing output
example :: (MonadIO m, MonadComputed m) => m ()
example = do
-- aliceRef :: Reference Person
aliceRef <- new $ Person { name = "Alice" }
-- alice :: Computed Person
alice <- track aliceRef
bobRef <- new $ Person { name = "Bob" }
bob <- track bobRef
-- companyRef :: Reference Company
companyRef <- new $ Company { legalName = "Eve's Surveillance" }
-- company :: Computed Company
company <- track companyRef
let dumpValues = do
(liftIO . print) =<< runComputed alice
(liftIO . print) =<< runComputed bob
(liftIO . print) =<< runComputed company
(liftIO . putStrLn) ""
dumpValues
people <- share $ Traversable.sequenceA [alice, bob]
structure2 <- share $ do
a <- alice
c <- company
return (a, c)
structure3 <- share $ (pure (,)) <*> structure2 <*> bob
let dumpStructures = do
(liftIO . print) =<< runComputed people
(liftIO . print) =<< runComputed structure2
(liftIO . print) =<< runComputed structure3
(liftIO . putStrLn) ""
dumpStructures
set aliceRef Person { name = "Mike" }
dumpValues
dumpStructures
set companyRef Company { legalName = "Mike's Meddling" }
dumpValues
dumpStructures
-- Run it again to demonstrate that only one get is required to get the entire pure structure
dumpStructures
set aliceRef Person { name = "Alice Again" }
dumpValues
dumpStructures
set bobRef Person { name = "Bob doesn't touch structure2" }
dumpValues
dumpStructures
-- Class for a typed dictionary in a monadic context
class (Monad m) => MonadReference m where
type Reference :: * -> *
new :: (Typeable a) => a -> m (Reference a)
set :: (Typeable a) => Reference a -> a -> m ()
get :: (Typeable a) => Reference a -> m a
-- Class for a monad with state dependent values
class (MonadReference m, Applicative Computed, Monad Computed) => MonadComputed m where
type Computed :: * -> *
track :: (Typeable a) => Reference a -> m (Computed a)
share :: (Typeable a) => (Computed a) -> m (Computed a)
runComputed :: (Typeable a) => (Computed a) -> m a
-- A published value for IO, using Weak references to the subscribers
data Published a = Published {
valueRef :: IORef a,
subscribers :: IORef [Weak (IO ())]
}
-- A new implementation that keeps an update list
instance MonadReference IO where
type Reference = Published
new = newIORefPublished
set = setIORefPublished
get = readIORefPublished
-- Separate implemenations for these, since we'd like to drop the Typeable constraint
newIORefPublished value =
do
ref <- newIORef value
subscribersRef <- newIORef []
return Published { valueRef = ref, subscribers = subscribersRef }
setIORefPublished published value =
do
writeIORef (valueRef published) value
notify $ subscribers published
--readIORefPublished = readIORef . valueRef
readIORefPublished x = do
putStrLn "getting"
readIORef $ valueRef x
notify :: IORef [Weak (IO ())] -> IO ()
notify = go
where
go subscribersRef = do
subscribers <- readIORef subscribersRef
needsCleanup <- (liftM (any id)) (mapM notifySubscriber subscribers)
when needsCleanup $ cleanupWeakRefs subscribersRef
notifySubscriber weakSubscriber = do
maybeSubscriber <- deRefWeak weakSubscriber
case maybeSubscriber of
Nothing -> return True
Just subscriber -> subscriber >> return False
cleanupWeakRefs :: IORef [Weak a] -> IO ()
cleanupWeakRefs ref = do
weaks <- readIORef ref
newWeaks <- (liftM catMaybes) $ mapM testWeak weaks
writeIORef ref newWeaks
where
testWeak weakRef = liftM (>> Just weakRef) $ deRefWeak weakRef
-- Data type for building computations
data IORefComputed a where
Pure :: a -> IORefComputed a
Apply :: IORefComputed (b -> a) -> IORefComputed b -> IORefComputed a
Bound :: IORefComputed b -> (b -> IORefComputed a) -> IORefComputed a
Tracked :: Published a -> IORefComputed a
Shared :: Published (Either (IORefComputed a) a) -> IORefComputed a
instance Monad IORefComputed where
return = Pure
(>>=) = Bound
(>>) _ = id
instance Applicative IORefComputed where
pure = return
(<*>) = Apply
instance Functor IORefComputed where
fmap = (<*>) . pure
-- Evaluate computations built in IO
instance MonadComputed IO where
type Computed = IORefComputed
track = trackIORefComputed
runComputed = evalIORefComputed
share = shareIORefComputed
-- Separate implementations, again to drop the Typeable constraint
trackIORefComputed = return . Tracked
evalIORefComputed :: IORefComputed a -> IO a
evalIORefComputed c =
case c of
Pure x -> return x
Apply cf cx -> do
f <- evalIORefComputed cf
x <- evalIORefComputed cx
return (f x)
Bound cx k -> do
value <- evalIORefComputed cx
evalIORefComputed (k value)
Tracked published -> readIORefPublished published
Shared publishedThunk -> do
thunk <- readIORefPublished publishedThunk
case thunk of
Left computation@(Bound cx k) -> do
x <- evalIORefComputed cx
-- Make a shared version of the computed computation
currentExpression <- shareIORefComputed (k x)
let gcKeyedCurrentExpression = Left currentExpression
writeIORef (valueRef publishedThunk) gcKeyedCurrentExpression
markDirty <- makeMarkDirty publishedThunk gcKeyedCurrentExpression computation
subscribeTo currentExpression markDirty
evalIORefComputed c
Left computation -> do
value <- evalIORefComputed computation
writeIORef (valueRef publishedThunk) (Right value)
return value
Right x ->
return x
shareIORefComputed :: IORefComputed a -> IO (IORefComputed a)
--shareIORefComputed c = return c
shareIORefComputed c =
case c of
Apply cf cx -> do
sharedf <- shareIORefComputed cf
sharedx <- shareIORefComputed cx
case (sharedf, sharedx) of
-- Optimize away constants
(Pure f, Pure x) -> return . Pure $ f x
_ -> do
let sharedc = sharedf <*> sharedx
published <- newIORefPublished $ Left sharedc
-- What we are going to do when either argument changes
markDirty <- makeMarkDirty published published sharedc
subscribeTo sharedf markDirty
subscribeTo sharedx markDirty
return $ Shared published
Bound cx k -> do
sharedx <- shareIORefComputed cx
case cx of
-- Optimize away constants
(Pure x) -> shareIORefComputed $ k x
_ -> do
let dirtyc = sharedx >>= k
published <- newIORefPublished $ Left dirtyc
-- What we are going to do when the argument to k changes
markDirty <- makeMarkDirty published published dirtyc
subscribeTo sharedx markDirty
return $ Shared published
_ -> return c
makeMarkDirty :: Published (Either (IORefComputed a) a) -> k -> IORefComputed a -> IO (Weak (IO ()))
makeMarkDirty published key definition =
do
let markDirty = do
existing <- readIORef (valueRef published)
case existing of
Right _ -> setIORefPublished published $ Left definition
_ -> return ()
mkWeak key markDirty Nothing
subscribeTo :: IORefComputed a -> Weak (IO ()) -> IO ()
subscribeTo (Tracked published) trigger = modifyIORef' (subscribers published) (trigger :)
subscribeTo (Shared published) trigger = modifyIORef' (subscribers published) (trigger :)
subscribeTo _ _ = return ()