I am trying to write an FRP library in Idris. This library designed to be run in a single thread environment. Future implement Monad
and Align
. I want to implement functions with these simplified type signatures:
createFutureResolveLater : (Future a, a -> IO ()) -- later time
createFutureResolveNow : a -> IO (Future a) -- current time
pure : a -> Future a -- before everything
align : Future a -> Future a -> Future (These a b) -- min of two times
join : Future (Future a)) -> Future a -- max of two times
exeEvent : Future (IO a) -> IO (Future a) -- ??
simultaneous : Future a -> Future b -> Future Bool -- min of two times
subFuture : Future a -> (a -> IO ()) -> IO ()
In seems obvious to me that
simultaneous a a, simultaneous a (fmap f a), etc.
should be a future that resolves to true.
In
do
oe <- exeEvent ie
let s = simultaneous oe ie
...
should s resolve to true?
In reactive-banana, s would be false. See mapEventIO in http://hackage.haskell.org/package/reactive-banana-1.2.1.0/docs/Reactive-Banana-Frameworks.html
In reflex, s would also mostly likely be false. See performEvent in https://daig.github.io/reflex/Reflex-PerformEvent-Class.html
Why do these library authors choose for s to be false?
Trying to figure out what you are asking. I think you are wondering how to account for IO
actions that take time, no? Because if the IO
action was instantaneous this would not be a question. Though there is still a question for me, which is does the IO
action block until the future occurs or does it schedule it asynchronously?
If this is a non-blocking call, I would say it should wait until the IO
action completes. (I would also call it schedFuture
or some such to be clear that it doesn't block.) Otherwise you could have the case of the Future
's time being in the past, yet the value still not being available, which could screw up the logic of functions that merge events, etc.
If this is a blocking call, I would instead provide
waitFuture :: Future a -> IO a
and let the user deal with the rest (e.g. they could fmap (const x)
on the original future if they wanted to preserve the time, they could use now :: IO (Future ())
if they wanted it when the action completes, etc.). It might be worth providing combinators for those if you can come up with good names for them, otherwise they are easy enough that I'd just punt.
I have also found it helpful when making systems like this to distinguish between "morally instantaneous" IO
actions and actions that might block. I suppose this is parallel to javascript's distinction between async and regular stack calls. There are a lot of fuzzy questions like this, and the corresponding obvious answers tend to depend on this distinction.