Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Haskell equivalent of C# 5 async/await

I just read about the new way to handle asynchronous functions in C# 5.0 using the await and async keywords. Examle from the C# reference on await:

private async Task SumPageSizesAsync()
{
    // To use the HttpClient type in desktop apps, you must include a using directive and add a 
    // reference for the System.Net.Http namespace.
    HttpClient client = new HttpClient();
    // . . .
    Task<byte[]> getContentsTask = client.GetByteArrayAsync(url);
    byte[] urlContents = await getContentsTask;

    // Equivalently, now that you see how it works, you can write the same thing in a single line.
    //byte[] urlContents = await client.GetByteArrayAsync(url);
    // . . .
}

A Task<byte[]> represents the Future of an asynchronous task that will generate a value of type byte[]. Using the keyword await on a Task will basically put the rest of the function in a continuation which will be called when the task is done. Any function that uses await must use the keyword async and have type Task<a> if it would return type a.

So the lines

byte[] urlContents = await getContentsTask;
// Do something with urlContents

would translate into something like

Task newTask = getContentsTask.registerContinuation(
               byte[] urlContents => {
                 // Do something with urlContents
               });
return newTask;

This feels a lot like a Monad (-transformer?). It feels like it should have some relation to the CPS monad, but maybe not.

Here is my attempt at writing corresponding Haskell types

-- The monad that async functions should run in
instance Monad Async
-- The same as the the C# keyword
await         :: Async (Task a) -> Async a
-- Returns the current Task, should wrap what corresponds to
-- a async method in C#.
asyncFunction :: Async a -> Async (Task a)
-- Corresponds to the method Task.Run()
taskRun       :: a -> Task a

and a rough translation of the above example

instance MonadIO Async -- Needed for this example

sumPageSizesAsync :: Async (Task ()) 
sumPageSizesAsync = asyncFunction $ do
    client <- liftIO newHttpClient
    -- client :: HttpClient
    -- ...
    getContentsTask <- getByteArrayAsync client url
    -- getContentsTask :: Task [byte]
    urlContents <- await getContentsTask
    -- urlContents :: [byte]

    -- ...

Would this be the corresponding types in Haskell? Is there any Haskell library this (or a similar way) implements way to handle asynchronous functions/actions?

Also: Could you build this using the CPS-transformer?

Edit

Yes, the Control.Concurrent.Async module does solve a similar problem (and has a similar interface), but does so in an entirely different way. I guess that Control.Monad.Task would be a closer match. What (I think) I am looking for is a monadic interface for Futures that uses Continuation Passing Style behind the scenes.

like image 732
Hjulle Avatar asked Nov 30 '13 01:11

Hjulle


People also ask

Is Haskell better than C?

Haskell is very competitive with C, and on some benchmarks, it is faster. In C, it's strict evaluation, you put a function there, and it executes one line at a time. It's how you should think of C executing.

Is Haskell harder than Python?

Haskell is considered a very hard language to learn and master. On the other hand, Python is considered the easiest and most useful programming language to use.

Is C -- a programming language?

C-- (pronounced C minus minus) is a C-like programming language. Its creators, functional programming researchers Simon Peyton Jones and Norman Ramsey, designed it to be generated mainly by compilers for very high-level languages rather than written by human programmers.


1 Answers

Here's a Task monad that builds on top of the async library:

import Control.Concurrent.Async (async, wait)

newtype Task a = Task { fork :: IO (IO a) }

newTask :: IO a -> Task a
newTask io = Task $ do
    w <- async io
    return (wait w)

instance Monad Task where
    return a = Task $ return (return a)
    m >>= f  = newTask $ do
        aFut <- fork m
        a    <- aFut
        bFut <- fork (f a)
        bFut

Note that I haven't checked the monad laws for this, so it might not be correct.

This is how you would define primitive tasks that run in the background:

import Control.Concurrent (threadDelay)

test1 :: Task Int
test1 = newTask $ do
    threadDelay 1000000  -- Wait 1 second
    putStrLn "Hello,"
    return 1

test2 :: Task Int
test2 = newTask $ do
    threadDelay 1000000
    putStrLn " world!"
    return 2

Then you can combine Tasks using do notation which creates a new deferred task ready to be run:

test3 :: Task Int
test3 = do
    n1 <- test1
    n2 <- test2
    return (n1 + n2)

Running fork test3 will spawn the Task and return a future which you can invoke at any time to demand the result, blocking if necessary until done.

To show that it works, I'll do two simple tests. First, I'll fork test3 without demanding its future just to make sure it spawns the composite thread correctly:

main = do
    fork test3
    getLine -- wait without demanding the future

This works correctly:

$ ./task
Hello,
 world!
<Enter>
$

Now we can test what happens when we demand the result:

main = do
    fut <- fork test3
    n   <- fut  -- block until 'test3' is done
    print n

... which also works:

$ ./task
Hello,
 world!
3
$
like image 76
Gabriella Gonzalez Avatar answered Oct 18 '22 01:10

Gabriella Gonzalez