-
Notifications
You must be signed in to change notification settings - Fork 2
/
Host3.hs
170 lines (143 loc) · 5.65 KB
/
Host3.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
{-
Copyright : (c) Dave Laing, 2016
License : BSD3
Maintainer : [email protected]
Stability : experimental
Portability : non-portable
-}
{-# LANGUAGE RankNTypes #-}
module Host3 (
go3
) where
import Data.Maybe (isJust)
import Control.Monad (unless)
import Control.Monad.Identity (Identity(..))
import Control.Monad.IO.Class (liftIO)
import Data.IORef (readIORef)
import System.IO
import Data.Dependent.Sum
import Reflex
import Reflex.Host.Class
-- I'm going to assume that you've read through Host2.hs prior to this.
-- We're going to introduce a more complex interface to our application in this example.
-- We have our input events:
data Input t = Input {
-- where ieOpen fires when the application starts
ieOpen :: Event t ()
-- and ieRead fires whenever the user enters a line of text
, ieRead :: Event t String
}
-- We also have output events:
data Output t = Output {
-- where oeWrite is fired to signal that we should print a line of text to the screen
oeWrite :: Event t String
-- and ieQuit is fired to signal that we should exit the application
, oeQuit :: Event t ()
}
-- Our new application type connects these together.
type SampleApp3 t m =
( Reflex t
, MonadHold t m
) => Input t
-> m (Output t)
-- This leads to our first sample application that isn't indisputably 100% boring.
guest :: SampleApp3 t m
guest (Input eOpen eRead) = do
let
-- If the user types something other than "/quit", we interpret that as a message.
eMessage = ffilter (/= "/quit") eRead
-- If the user types "/quit", we should probably exit.
eQuit = () <$ ffilter (== "/quit") eRead
-- We'll be polite, and issue greeting and parting messages to the user.
-- Other than that we'll just be echoing their input up until they quit.
-- Perhaps it's 99% boring, but it's progress.
eWrite = leftmost [
"Hi" <$ eOpen
, ("> " ++) <$> eMessage
, "Bye" <$ eQuit
]
return $ Output eWrite eQuit
-- This is the code that runs our FRP applications.
host :: (forall t m. SampleApp3 t m)
-> IO ()
host myGuest =
runSpiderHost $ do
(eOpen, eOpenTriggerRef) <- newEventWithTriggerRef
(eRead, eReadTriggerRef) <- newEventWithTriggerRef
Output eWrite eQuit <- runHostFrame $ myGuest $ Input eOpen eRead
hWrite <- subscribeEvent eWrite
hQuit <- subscribeEvent eQuit
liftIO $ hSetBuffering stdin LineBuffering
-- Everything up to here should be familiar.
-- The rest of the code is similar to what we same in Host2.hs, but
-- this time I've refactored a bit so that I don't have to repeat
-- myself as much.
-- The plan is that we're going to fire the 'eOpen' event, and then
-- enter into a loop where we read a line from the user, then fire
-- the 'eRead' event over and over.
--
-- We're trying to write a general host here, so we can't assume
-- anything about the output events that we're listening to.
--
-- It could be the case that we fire the 'eOpen' event and the
-- 'eQuit' event is fired immediately in return. We have to handle
-- anything that the deranged mind of a user might throw at us.
--
-- For this particular host, we want to respond in the same way to
-- the events that we read after firing either of the input events.
-- To that end, we separate out the common bits.
let
-- We have a piece of code that reads from the event handles:
readPhase = do
-- This version of the host reads from both events and
-- returns both values, regardless of what they are.
mWrite <- readEvent hWrite >>= sequence
mQuit <- readEvent hQuit >>= sequence
-- If it matched what we wanted from our host, we could have
-- read from the quit event and suppressed the results of the
-- write event if the quit event had fired at the same time.
--
-- It seems arbitrary here, but in other domains it could be
-- just what you want to prevent a write-after-close problem.
return (mWrite, mQuit)
-- We have a piece of code that responds to our output events:
handleOutputs mWrite mQuit = do
case mWrite of
Nothing -> return ()
-- If we had a write event, print the 'String' value from
-- event:
Just w -> liftIO . putStrLn $ w
-- We can do this a little more simply with:
-- forM_ mWrite $ liftIO . putStrLn
-- Convert the occurrence of the quit event into a 'Bool':
return $ isJust mQuit
-- We have a piece of code to fire an event and deal with the
-- response from the output events.
fireAndProcess t v = do
mETrigger <- liftIO $ readIORef t
(mWrite, mQuit) <- case mETrigger of
Nothing ->
return (Nothing, Nothing)
Just eTrigger ->
fireEventsAndRead [eTrigger :=> Identity v] readPhase
-- This will return a 'Bool' indicating whether the quit event
-- has fired.
handleOutputs mWrite mQuit
-- We have a piece of code that uses that to guard some kind
-- of continuation:
fireProcessAndLoop t v k = do
quit <- fireAndProcess t v
unless quit
k
-- We use 'fireProcessAndLoop' to define our main event loop:
loop = do
input <- liftIO getLine
fireProcessAndLoop eReadTriggerRef input loop
-- and we also use it to fire off our open event and start the
-- event loop:
fireProcessAndLoop eOpenTriggerRef () loop
-- Now we can run our sample application ('guest') using
-- our code for hosting this kind of applications ('host').
go3 :: IO ()
go3 =
host guest