Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Writing composable asynchronous monads from ffi

Tags:

purescript

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.

like image 639
Fresheyeball Avatar asked Aug 21 '14 17:08

Fresheyeball


1 Answers

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.

like image 170
Phil Freeman Avatar answered Oct 21 '22 22:10

Phil Freeman