Concurrency

Because Egel is a term rewrite language, it is trivial to rewrite terms in parallel. Concurrency is provided through a parallel combinator and a process abstraction.

Parallel rewriting

If you want to have two computations run in parallel use the par combinator. It takes two abstractions to be reduced, applies both of them to a dummy argument, and returns a tuple containing both results.

>> using System
>> par [ _ -> 1 + 2 ] [ _ -> 3 + 4 ]
(System:tuple 3 7)

Note

The par combinator takes two abstractions because Egel has strict semantics. If it would have been just par (1+2) (3+4) the interpreter would have reduced the arguments to par first, resulting in the parallel reduction of par 3 7. By wrapping the computations their evalution is deferred.

We can inspect what arguments are given to the abstractions of par.

>> using System
>> par [ X -> X ] [ X -> X ]
(System:tuple System:nop System:nop)

Hardly interesting.

Of course, you might want to supply arguments to both terms to be reduced. Then simply wrap them in an abstraction again.

>> using System
>> [ X -> par [ _ -> X * 3 ] [ _ -> X + 5 ] ] 4
(System:tuple 12 9)

Parallel Fibonacci

With all what we know now, we can implement parallel Fibonacci.

import "prelude.eg"

namespace Fibonnaci (
  using System

  def pfib =
    [ 0 -> 0
    | 1 -> 1
    | X -> [ (F0, F1) -> F0 + F1 ] (par [_ -> pfib (X - 1) ] [_-> pfib (X - 2)]) ]

)

using Fibonnaci
using System

def main = pfib 10

In the recursive alternative of pfib it will start up two parallel computations, reduce those, after which it will deconstruct the pair returned and add both components.

Nifty, huh?

Caution

Though morally Egel could support cheap concurrency, the par combinator is implemented with the C++ thread library, thus with system threads.

System threads are a bit heavyweight and easy to run out of. On my machine, I can start upto roughly 20,000 threads. Go easy on pfib!

Processes

Process abstractions model ‘mealy machines’, for every input provided an output, and a continuation, is generated. A simple example is provided below.

> def id = [ MSG -> (MSG,  id) ]
> val i = proc id
> send i "hello"
> recv i
"hello"
> halt i

Note the use of val, reduction before assignment will start one process on the background. This is a simple example, in most use cases likely in the continuation a state will be passed around.