Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Async/await #13

Open
weswigham opened this issue Aug 31, 2014 · 2 comments
Open

Async/await #13

weswigham opened this issue Aug 31, 2014 · 2 comments

Comments

@weswigham
Copy link
Contributor

C# started it, ES6 has it, even C++ 14 has it. Async/await is an amazingly simple construct for creating asynchronous code. Weather it's implemented with promises or continuations on the back end, we'll see later (likely continuations, since I see no indication that Brick will be event-loop based).

Regardless, the async/await keyword syntax for defining asynchronous code is a godsend; it helps create self-documenting code, simplifies asynchronous control flow, and fits snugly with already-defined chunks of brick syntax:

#=
A super-simple asynchronous IRC bot, to experiment with Brick
#=
import Net.Socket.TCP
server = "irc.freenode.org"
port = 6667
nick = "Brick"
channel = "#brick_bot"
commands = {
    "!quit": |sock:Socket, _:Array<String>| async {
        await sock.write("QUIT :Exiting")
        await sock.destroy!
        exit(0)
    },
    "!id": |sock:Socket, words:Array<String>| async -> boolean {
        await sock.write("PRIVMSG " + channel + " :" + words.implode(" "))
    }
}
async listener(sock:Socket, s:String)
    if s.substring(0, 6) == "PING :"
        await sock.write("PONG :" + s.substring(6, s.length))
    else
        let p = s.find(":")
            cs = s.substring(p, s.length)
            exp = cs.explode(" ")
        in
            if commands.has_key?(exp[0])
                puts(exp[0] + "> " + exp.drop(1).implode(" "))
                await commands.get(exp[0])(sock, exp.drop(1))
            else
                puts("> " + cs)
            end
        end
    end
end
async main
    let !sock = await TCP(server, port) in
        if not !sock.success?
            puts("Failed to connect to server")
        else
            await !sock.write("NICK " + nick)
            await !sock.write("USER " + nick + " 0 * :" + nick + " bot")
            await !sock.write("JOIN " + channel)
            sock.listen!(listener)
        end
    end
end

Important notes on what I see as proper use: async isn't an used like an access modifier like in C++ or C#, instead, it's an alternate keyword for function declaration, which implies that the return type should be wrapped in an Awaitable Promise/Future. This way, if manually specifying a type declaration for an async, you needn't include that yourself. In effect, the async keyword adds the type -> Awaitable<T> to the end of the type line. You'll notice that for lambdas, the async keyword appears after the argument types. If I specifying the lambda's return type, it would appears before that. This makes sense, since in practice, the keyword modifies the return type of the function.

As far as the specifics on how async/await interact with threading/blocking, the C# documentation section is excellent: http://msdn.microsoft.com/en-us/library/hh191443.aspx#BKMK_Threads

@toroidal-code
Copy link
Member

I'm sorry but I don't really see why this is needed. Can you elaborate?

@weswigham
Copy link
Contributor Author

It's a shorthand for specifying things that are asynchronous - it clarifies asynchronous code far better than

spawn(proc() {
    x = x + 1
})

Which is difficult to know when has been completed (without inefficient busy-loops, or nested promises/callbacks). It's never been required, per sey, it's not a necessity to use it to write async code in C#, C++, or ES6, but it makes the code you do write wit it far easier to understand. It also adds another layer where the compiler can optimize asynchronous calls.

Specifically, in the above IRC example, now that everything is being awaited, even in a single-threaded process, multiple commands can now be processed simultaneously if they wait on external resources, since the listen! handler no longer blocks. If I were to implement the listen! loop myself, it would (a little simplified) look like:

async method listen(handle: Awaitable<Socket, String, Unit>)
    loop
        let line = await self.getline(MAX_LENGTH)
        in
            handle(self, line)
        end
    end
end

Note how the call to handle is not awaited, thereby letting handlers execute concurrently (even in a single-threaded scenario, since the scheduler will yield while self.getline is waiting on I/O). This is a really great pattern that allows for simple concurrency and efficient handling of IO bound work in a functional style (even if under the hood it's not very functional), and I think it'd be a great idea if it was included in Brick.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

2 participants