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