Ko

A language for programming recursive circuits

Ko
2. Language
2.4. Macros
2.4.3. Macros for control
2.4.3. Macros for control

Yield

Yield recognizes three arguments: if (boolean), then and else . If if is true , Yield returns the value of then , otherwise it returns the value of else . If either of then or else is omitted, an empty value is returned in the respective case.

Say(hello) {
	return: Show(
		Yield(
			if: hello   // hello must be boolean
			then: "hello"
			else: "good-bye"
		)
	)
}

Loop

Loop recognizes three arguments: start , step and stop , of which only step is required.

Loop invokes the step variety repeatedly in a loop. On each iteration, it passes a “carry” value returned from the previous iteration as a monadic argument to step . On the first iteration, Loop passes the value of start instead.

If stop is provided, it must be a variety with one monadic argument, returning a boolean. On each iteration, after the invocation of step completes, the returned carry value is passed to stop . If stop returns true , the loop terminates and Loop returns the last carry. If stop returns false , the loop continues.

The following function enumerates all integers, starting from 0:

import "integer"

ListAllIntegers() {
	return: Loop(
		start: 0
		step: printAndIncrement(n?) {
			_: Show(n)
			return: integer.Sum(n, 1)
		}
	)
}

The following function enumerates all integers up to a given limit:

import "integer"

ListIntegersUpTo(limit) {
	return: Loop(
		start: 0
		step: printAndIncrement(n?) {   // printAndIncrement is defined inline
			_: Show(n)
			return: integer.Sum(n, 1)
		}
		stop: ifNotSmallerThan(limit, n?) {   // ifNotSmallerThan is defined inline
			return: integer.Less(limit, n)
		} [limit: limit]   // fix the limit argument of ifNotSmallerThan
	)
}

Spin and Wait

Spin recognizes a monadic (unnamed) argument which must be a variety. It executes the variety (a function lambda) in a new process (co-routine) and immediately returns a handle to the executing process.

The returned handle is a structure with a single field named Wait . Invoking Wait blocks until the executed process terminates and returns the return value of the function executing inside. Repeated calls to Wait return immediately with the value returned on the first call.

Wait returns a structure value with two fields: success and returned . success is boolean and equals true unless the execution resulted in a panic. returned holds the returned value.

In the following example, two processes are started: one printing "hello" and one printing "world" . The enclosing function does not return until both have finished.

TwoInParallel() {
	p1: Spin(Print["hello"])
	p2: Spin(Print["world"])
	return: (
		returned1: p1.Wait()
		returned2: p2.Wait()
	)
}

Parallel

By default, circuit functions are executed sequentially (in topological order with respect to its directed acyclic structure). The Parallel macro, when applied to a circuit function variety, executes that circuit in parallel.

When a circuit starts executing in parallel, new processes are allocated for each of its elements. Individual circuit elements (which are themselves invocations to other circuits or macros) begin execution as soon as their arguments arrive from upstream.

The typical use of Parallel is to create a parallel version of a circuit, as in the following example.

DoSomeWork(name) {
	return: (
		job1: Print("hello")
		job2: Print("world")
		job3: Print(name)
	)
}

DoSomeWorkInParallel(name) {
	return: Parallel(
		DoSomeWork[name: name]
	)
}

Invoking DoSomeWorkInParallel will cause the Print elements (computing job1 , job2 and job3 ) to be executed in parallel.