Ko

A language for programming recursive circuits

Ko
2. Language
2.6. Syntax
2.6. Syntax

The root of your Ko repository currently defaults to $GOPATH/src . In other words it coincides with the root of your Go repository. This ensures that the (fully-qualified) names of Go transform implementations (called gates ) and Ko functions reside in the same namespace for simplicity.

Source file

Ko source files have a .ko extension. A collection of .ko files in the same directory forms a package. Unlike Go, Ko source files do not contain a package name definition.

A Ko source file is a sequence of import statments (denoted <Import> ), function definitions (denoted <Func> ) and comments (denoted <Comment> ), separated by new lines (denoted <NewLine> ).

Comments, whitespace and literals

Ko sources support Go-style (i.e. C-style) comments: both comment-blocks /* ... */ , and end-of-line comments // ... .

Outside of comments, the Ko syntax recognizes two types of whitespace. <Space> is any non-empty sequence of spaces or tabs. <NewLine> is any non-empty sequence of comma, semicolon or the ASCII new line character.

There are four types of constant literals: boolean, string, integer and floating-point. All four literals follow the corresponding Go syntax. Booleans are true or false . Strings can be single-line double-quoted "..." (containing escape sequences) or multi-line backquoted `...` (treated verbatim). Integral and floating-point constants can be signed or unsigned, e.g. -2018 and 2.1e-37 .

Import statements

An import statement provides access to the Ko functions (and Go transformations) defined within a referenced package path. Importing has two alternative forms. <Import> without specifying a package alias conforms to the syntax:

import <PkgPath>

<Import> with a specified package alias conforms to:

import <PkgPath> as <PkgAlias>

In both cases, <PkgPath> is a string literal containing the repo-based package directory path. If an <Alias> is not specified, the last part of the package path directory is used as <Alias> .

Functions defined in the imported package can be referenced as

<Alias>.<FuncName>

Cyclical imports

Unlike other languages, Ko allows cyclical package imports: two packages using each other's function definitions.

Function (circuit) defintion

A function definition, <FuncDef> , conforms to the syntax:

<FuncName>(<FuncArgs>) {
	<Label>: <Formula>   // zero or more labelled formulas
	return: <Formula>   // mandatory return formulas
}

<FuncName> is an identifier, which must be unique in its package scope.

Arguments

<FuncArgs> is a list of zero or more <Arg> (argument) declarations, separated by <NewLine> s. Each <Arg> in the list <FuncArgs> must be a unique identifier with respect to the function scope. Here's an example function definition, eliding the body:

Max(x, y) {
	return: ... // body elided
}

Subsequently when functions are called, their arguments are assigned explicitly by providing the argument names. For instance, the function above could be called as Max(x: 3, y: -5) .

In a <FuncArgs> list, one argument may be designated as monadic (aka “default”) by appending a question mark character, ? , right after the argument's identifier. For example,

JoinStrings(strings?, delimiter) {
	return: ... // body elided
}

Functions that have a monadic argument can be called in two ways. Either by providing argument names explicitly, e.g. JoinStrings(strings: ("a", "b"), delimiter: ",") , or by supplying only the default argument and eliding argument labels, e.g. JoinStrings(("a", "b")) . The latter can also be written as JoinStrings("a", "b") , because the lack of labels makes this simplification unambiguous.

Formulas

<Formula> s are the building block of functions. A formula describes how to obtain a value. Simple formulas directly refer to readily available values (e.g. the value of a local function argument or a constant). Transformation formulas specify how to derive a value from previously-available values.

Simple formulas

A simple formula is one of:

<ArgRef> , an argument identifier refering to the value of the corresponding function argument

<Literal> , a constant literal (described above) specifying a boolean, string, integer or floating-point constant.

<FuncRef> is a reference to a function, which is interpreted as a variety value (“variety” is the Ko nomenclature for a “lambda” or “closure”). Function references can be of two types. References to package-local functions entail simply using the function name identifier <FuncName> . References to imported functions are in the form <PkgAlias>.<FuncName> .

<MacroRef> , is a reference to a macro , which is also interpreted as a variety value. Macro references are simply the macro name identifier.

<FuncDef> , a function definition (defined above) is also a valid formula and it is interpreted as a variety value refering to the defined function. Note that, unlike in other languages, function definitions that are inline still need to have a unique package-wide name. They are not anonymous! For example:

ReturnsVarietyReturning8() {
	return: Return8() {
		return: 8
	}
}

Transformation formulas

There are three types of transformation formulas: selection, invocation and augmentation. They are all defined recursively (in terms of the <Formula> rule).

Invocation

Invocation formulas derive a new value by invoking a Ko transformation (provided as a variety value) to a set of zero or more argument values.

In the simplest case, an invocation formula invokes a Ko transformation without passing any arguments. Such formulas conform to the syntax:

<Formula>()

Note that the formula preceding the round brackets, () , must evaluate to a variety value for the invocation to be valid.

The most common type of invocation is one where argument values are passed to explicitly named arguments of the transformation to be applied. This conforms to the syntax:

<Formula>(
	<ArgName>: <Formula> // one or more argument assignments
)

Here, again, the formula preceding the opening round brackets, ( , must evaluate to a variety value for the invocation to be meaningful.

Finally, transformations that support monadic (default) arguments can be invoked using this simplified syntax:

<Formula>(<Formula>)

Here the formula preceding the opening round brackets, ( , must evaluate to a variety value (which refers to a transformation with a monadic argument) for the invocation to be meaningful. The formula inside the round brackets is passed to the monadic argument of the underlying transformation.

Augmentation

Augmentation is Ko nomenclature for “closure”, in the lingo of functional languages. Augmentation syntax is identical to invocation syntaxes (described above), except that the round braces, () , are replaced with square ones, [] .

For example, let F be a function with three named arguments, defined as

F(x, y, z) {
	return: ... // body elided
}

Then the formula F represents a variety refering to the function F (with no arguments set), and so does F[] .

On the other hand, F[x: 1] refers to F with the x argument set to 1 . And so it can be invoked, for instance by writing F[x: 1](y: 2, z: 3) .

Selection

<Selection> conforms to the syntax <Formula>.<FieldName> . Selection is valid only when the preceding formula evaluates to a structure-type value, where selection of a field value makes sense. For example, the function below returns "abc" :

ReturnFirstField() {
	return: (firstField: "abc", secondField: 123).firstField
}

Chaining

It is worth pointing out that the recursive nature of the rules for selection, invocation and augmentation formulas (in terms of them being <Formula> s, recursively defined in terms of other <Formula> s), imply that one can do “chaining”. Take for instance the following example:

F(flag) {   // flag is boolean
	return: Yield(
		if: flag
		then: (call: G)   // G is a function defined in “this” package
		else: (call: H)   // H is also a function defined in “this” package
	).call()
}

Here the invocation of the Yield macro returns the structure (call: G) if the argument flag is true , and (call: H) otherwise.

Following the invocation of Yield , the field call (of whichever structure was returned by Yield ) is selected and then immediately invoked by the round brackets () after call . Without chaining, F could be equivalently implemented as:

F(flag) {
	y: Yield(
		if: flag
		then: (call: G)
		else: (call: H)
	)
	return: y.call()
}