Right now I have an async function that works something like this:
foo = do
ayncGetNumber "/numberLocation" \a ->
(trace <<< show) a
but this callback style is not composable (to my understanding), i would like it to work like this
foo = do
a <- ayncGetNumber "/numberLocation"
(trace <<< show) a
or
foo = ayncGetNumber "/numberLocation" >>= show >>> trace
But I can't figure out how to escape from the callback and make it composable.
You might want to consider using the continuation monad transformer ContT from the purescript-transformers package.
The definition of ContT in that package is given as
newtype ContT r m a = ContT ((a -> m r) -> m r)
If you set r to be Unit, and m to be Eff eff, you get something which looks a bit like the type of asyncGetNumber inside the constructor:
(a -> Eff eff Unit) -> Eff eff Unit
Now, making sure eff contains the effect you need, you should be able to wrap up your
asyncGetNumber function for use in ContT:
asyncGetNumberCont :: ContT Unit (Eff SomeEffects) Number
asyncGetNumberCont = ContT $ \callback ->
asyncGetNumber "/numberLocation" callback
or just
asyncGetNumberCont = ContT $ asyncGetNumber "/numberLocation"
Note that the argument to ContT takes the callback as an argument.
You can now compose asynchronous computations in series using do notation:
do n <- asyncGetNumberCont
m <- asyncGetNumberCont
return (n + m)
You can even create an applicative functor which wraps ContT and supports parallel composition of asynchronous computations. You might also be interested in the purescript-node-thunk package which provides this sort of functionality out of the box.
Edit: Another option is to create your own ContT-like type using a foreign type, distinct from Eff. You might do this as follows:
-- Ignore effects to keep things simple
-- The runtime representation of 'Async a' is a function which takes a callback,
-- performs some side effects an returns.
foreign import data Async :: * -> *
-- Make an async computation from a function taking a callback
foreign import makeAsync
"function makeAsync(f) {\
\ return function(k) {\
\ f(function(a) {\
\ return function() {\
\ k(a)();\
\ };\
\ })();\
\ };\
\}" :: forall a eff. ((a -> Eff eff Unit) -> Eff eff Unit) -> Async a
-- Now we need to define instances for Async, which we can do using FFI
-- calls, for example:
foreign import fmapAsync
"function fmapAsync(f) {\
\ return function (comp) {\
\ return function (k) {\
\ comp(function(a) {\
\ k(f(a));\
\ });
\ };\
\ };\
\}" :: forall a b. (a -> b) -> Async a -> Async b
instance functorAsync :: Functor Async where
(<$>) = fmapAsync
and so on. This gets cumbersome quickly since you're essentially repeating the implementation of ContT.
In addition, without rewrite rule support in the compiler, there is no way to get the inlined binds like you would get with Eff.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With