3.22. kink/chan/CHAN

Provides chans, which are message queues between threads. Multiple chans can be waited for at one time.

Example:

:CHAN.require_from('kink/chan/')
:THREAD.require_from('kink/thread/')

# calculates the Nth fibonacci number in an inefficient algorithm
:fib <- {(:N)
  (N <= 1).if_else(
    { 1 }
    { fib(N - 1) + fib(N - 2) }
  )
}

# attempts to calculate a fibonacci number in 3 seconds,
# or timeout
:attempt_fib <- {(:N)
  :Ans_ch = CHAN.new(1)
  THREAD.spawn{
    :Ans = fib(N)
    CHAN.select(
      Ans_ch.send(Ans){}
    )
  }
  CHAN.select(
    Ans_ch.recv(
      {(:Ans) 'fib({}) is {}'.format(N Ans) }
      { raise('Ans_ch is never pinned') }
    )
    CHAN.timeout(3){ 'timeout' }
  )
}
stdout.print_line(attempt_fib(10))  # => fib(10) is 89
stdout.print_line(attempt_fib(20))  # => fib(20) is 10946
stdout.print_line(attempt_fib(100)) # => timeout

Chans are much the same as Golang channels, but there are several differences.

1) After closing a chan by Chan.pin(Pin), the Pin val can be read by recv. It is useful to implement futures or such.

2) Even after the chan is closed, Chan.send does not cause an exception, but the event is never fired. It could be useful in some use cases.

3) The mod is designed to be used as a base layer of frameworks such as worker pools, and not intended to be used directly from ordinary applications. This is why the mod lacks some convenient features such as chan-as-iter.

3.22.1. CHAN.select(... Events)

Wait until exactly one chan event in Events is fired.

Precondition:

* Events must contain one or more elements.

* Events from the same chan cannot appear twice or more in Events.

* nowait and timeout events can appear only once in Events.

3.22.2. CHAN.chan?(Val)

Returns whether Val is a chan.

3.22.3. CHAN.new(Capa)

Makes a new chan with Capa as the capacity of the buffer.

Precondition: Capa must be a non-negative int num.

3.22.4. Type chan

A message queue between threads. Multiple chans can be waited for at one time.

Chan.send(Msg $on_sent)

Returns a chan event which sends the Msg through the Chan, or puts the Msg into the buffer.

When the event is fired, $on_sent is tail-called with no args.

If the Chan has been already pinned, the event does not attempt to send the Msg, and the event is never fired.

Chan.recv($on_received $on_pinned)

Returns a chan event which dequeues a message from the buffer of the Chan, receives a message via the Chan, or waits until the Chan is pinned and all the enqueued messages are dequeued.

If the event is fired receiving a message from the Chan or its buffer, $on_received is tail-called with the message as an argument.

If the event is fired because the Chan is pinned, $on_pinned is tail-called with the current pin val.

Chan.pin(Pin)

Pins the Chan with the Pin val.

When the Chan is pinned, no new senders can be enqueued afterward. After all the messages are dequeued, $on_pinned is called for recv events on the Chan.

This method can be called multiple times for one Chan. Each invocation ovwrrites the current Pin val.

Note: this is similar to close(chan) in Golang, but it can also sets the Pin val.

3.22.5. CHAN.event?(Val)

Returns whether the Val is a chan event.

3.22.6. Type event

Chan event selected by CHAN.select.

It provides no public methods.

3.22.7. CHAN.nowait($body_thunk)

Returns a chan event which is fired when all other events in the selection have to block the thread.

If the event is fired, $body_thunk is tail-called with no args.

Note: this is similar to "default" clause of Golang select statement.

3.22.8. CHAN.timeout(Seconds $body_thunk)

Returns a chan event which is fired after the specified Seconds.

If the event is fired, $body_thunk is tail-called with no args.