Processes

Arza is heavily inspired by Erlang and uses its idea of processes as a concurrency tool.

Processes or actors or symmetric coroutines are independent universal primitives of concurrent computation.

They can exchange messages but can not share any data.

Arza syntax for process creation and message handling very similar to Erlang

//to spawn process
let pid = spawn(somefunc, args)

// get pid of current process
let this_pid = self()

// to receive messages from other processes
receive
    | clause1 = branch1
    | clause2 = branch2

// to kill process
close(pid)

Ping-Pong example

// usefull functions
import process

type PingPongFinished(store)

fun ping(n, pong_pid, store) =
    if n == 0 then
        // This is message sending
        pong_pid ! #finished
        store
    else
        // self() returns current pid
        pong_pid ! (#ping, self())
        receive #pong =
                ping(n-1, pong_pid, #pong :: store)

fun pong(store) =
    receive
        | #finished =
            // if excepion occures it became process result
            throw PingPongFinished(store)

        | (#ping, ping_pid) =
            ping_pid!#pong
            pong(#ping :: store)

    // this disables printing exceptions in processes to stderr
    process:set_error_print_enabled(pong_pid, False)
    let pong_pid = spawn(pong, ([],))
    // using currying just for clarity
    ping_pid = spawn .. ping .. (3, pong_pid, [])
    // waiting for all processes to end
    result = process:wait_for([pong_pid, ping_pid])
in
    affirm:is_equal(result.[ping_pid], [#pong, #pong, #pong])
    affirm:is_equal(result.[pong_pid], ValueError([#ping, #ping, #ping]))
    // closing
    close(pong_pid)
    close(ping_pid)

With symmetric coroutines it’s easy to implement asymmetric coroutines

// function from std lib
fun coro(fn) =
    let
        proc1 = self()
        proc2 = process:create()

        fun make_chan(pid) =
            fun (...args) =
                if is_finished(pid) then
                    throw CoroutineEmpty(result(pid))
                else
                    match args
                        | () = pid ! ()
                        | (m) = pid ! m
                    // receiving
                    receive msg = msg
        chan1 = make_chan(proc2)
        chan2 = make_chan(proc1)

        fun wrapper(...args) =
            let
                (res =
                    (try
                        fn(...args)
                    catch e = e
                    )
                )
            in
                proc1 ! res
                res

        fun _coro(...args) =
            if is_idle(proc2) then
                start(proc2, wrapper, (chan2,) ++ args)
                receive msg = msg
            else
                chan1(...args)
    in
        _coro

fun test_coro() =
    let
        fun test1() =
            let
                c = process:coro(fun(yield, start) =
                    (let
                        x = yield(start)
                    in
                        yield(x)
                    )
                )
                c1 = process:coro((yield) -> #zero)
            in
                affirm:is_equal .. c1() .. #zero
                affirm:is_throw(c1, ())

                affirm:is_equal .. c(#first) .. #first
                affirm:is_equal .. c(#second) .. #second
                affirm:is_equal .. c(#last) .. #last
                affirm:is_throw(c, ())
                affirm:is_throw(c, ())

        fun test2() =
            let
                c = process:coro(fun(yield) = throw 1)
            in
                affirm:is_equal .. c() .. 1
                affirm:is_throw(c, ())
    in
        test1()
        test2()

With processes you can create emmulation of mutable state

Example: Mutable State