2.1. Core concepts

This chapter describes concepts which are essential for execution of programs.

2.1.1. Runtime

The runtime is a system which executes Kink programs. The runtime itself is implemented on the Java Virtual Machine.

2.1.2. Values

A value is a unit of data, such as a number, a function or a collection of values. For example, each line of this example produces a value.

'Twift'
42
{ (:Num) Num * 2 }

2.1.2.1. Basic components of values

A value consists of these three components.

Variables

A variable is a labeled arrow to another value. A value can have arbitrary number of variables.

Symbols of variables are case-sensitive, hence a value can have both voxxx and VOXXX as distinct variables.

Parent

Every value except for the base value has a parent. The only base value has no parent.

If a value P is the parent of a value C, C is said to be a child of P.

When dereferencing a variable which the value does not own, the variable is searched within the parent. The search goes upward to the base value.

Boxed Java object

A value may contain a boxed Java object.

Look at the program below.

# Make a value which represents an account, with variables "Balance" and "balance".
:Account = new_value
Account::Balance = 0
Account::balance = { [:A]
    A.Balance
}

# Make a child of the account value, overriding "Balance" variable.
:Bank_account = Account.new_child
Bank_account::Balance = 42

# Call a function stored in "balance" variable for the values.
print_line(Account.balance)      # => 0
print_line(Bank_account.balance)  # => 42

The relationship of values in the program can be represented as the following graph. Each box in the graph represents a value, and each circle represents a Java object.

digraph values {
    graph [size = "6, 6"];
    node [shape = box];
    "Environment" -> "Account" [label = "variable\n\"Account\""];
    "Environment" -> "Bank Account" [label = "variable\n\"Bank_account\""];
    "Bank Account" -> "42" [label = "variable\n\"Balance\""];
    "Bank Account" -> Account [label = "parent", style = "dashed"];
    Account -> "Function\n{ [:A] A.Balance }" [label = "variable\n\"balance\""];
    Account -> "0" [label = "variable\n\"Balance\""];
    Account -> base [label = "parent", style = "dashed"];
    "0" -> java0 [label = "boxed", style = "dotted"];
    java0 [shape = ellipse, label = "java.lang.Integer 0"];
    "42" -> java42 [label = "boxed", style = "dotted"];
    java42 [shape = ellipse, label = "java.lang.Integer 42"];
}

2.1.2.2. Super values and sub values

Based on parent-child relationships, sub values and super values are defined as below.

Sub values
  • A value V is a sub value of V itself.
  • If the parent of V is a sub value of a value S, V is also a sub value of S.
Super values
  • If a value V is a sub value of S, S is a super value of V.

Suppose parent-child relationships of values are as below.

digraph super_and_sub {
    graph [size = "6, 6"];
    node [shape = box];
    "Bank Account" -> "Account" [label = "parent", style = "dashed"];
    "Brokerage Account" -> "Account" [label = "parent", style = "dashed"];
    "Account" -> "base" [label = "parent", style = "dashed"];
    "Bond" -> "base" [label = "parent", style = "dashed"];
}

Among the values, these relationships hold.

  • Bank Account is a sub value of Bank Account itself, Account and base.
  • Brokerage Account is a sub value of Brokerage Account itself, Account and base.
  • Account is a sub value of Account itself and base.
  • Bond is a sub value of Bond itself and base.
  • base is a sub value of base itself.

These relationships also hold.

  • base is a super value of all values including base itself.
  • Bond is a super value of Bond itself.
  • Account is a super value of Account itself, Bank Account and Brokerage Account.
  • Bank Account is a super value of Bank Account itself.
  • Brokerage Account is a super value of Brokerage Account itself.

If both of a value A and another value B are super values of V, and A is a sub value of B, A is said to be the nearer super value, and B is said to be the farther super value. Consequently, for every value, the value itself is the nearest super value, and base is the farthest super value.

Kink has no intrinsic type system, neither dynamic nor static. Inheritance is done by super-sub relationships among values.

2.1.3. Variables

A variable is a labeled arrow from a value to another value. The label of a variable is said to be a symbol. The value at the tail of the arrow is said to be the owner of the variable, and the owner is said to own the variable. The value at the head of the arrow is said to be the target of the variable.

A value can have at most one variable with a particular symbol. Hence a variable can be identified by the owner and the symbol.

2.1.3.1. Symbols of variables

A symbols of a variable can be an arbitrary string. Variables with the following two kinds of symbols can be used directly in Kink programs. The other kinds of variables can also be accessed via var function.

Nouns

A noun starts with an upper case letter (A-Z), and is followed by letters (a-zA-Z), digits (0-9), underscores (“_”) or question marks (”?”). For example: ArrayList, MAX_VALUE and More_lines?.

Normally used for variables containing data.

Verbs

A verb starts with a lower case letter (a-z) or an underscore (“_”), and is followed by letters (a-zA-Z), digits (0-9), underscores (“_”) or question marks (”?”). For example: any?, _loop and getClassLoader.

Normally used for variables containing functions.

Nouns and verbs are dealt differently in the syntax. When you dereference a variable with a noun Data from the owner Owner, you can write Owner.Data. When you dereference a variable with a verb fun from the same owner, you can write Owner$$fun. If you write Owner.fun, it calls a function within the variable. Here is an example.

:Owner = new_value
Owner::Data = 42
Owner::fun = { 'Voxxx' }

print_line(Owner.Data)  # => 42
print_line(Owner$$fun)  # => { 'Voxxx' }
print_line(Owner.fun)   # => Voxxx

By that distinction in the syntax, programmers are encouraged to use nouns for mere data, and verbs for functions, although it is not mandatory.

2.1.3.2. Variable references

A variable reference is a value which represents a variable. Operations such as assignment or existence checking are done through variable references.

Variable references can be typically created by local reference expressions or attributional reference expressions. For example, this expression creates a reference of a variable Data, whoes owner is Owner.

Owner::Data

This expression creates a reference of a variable fun, whoes owner is Owner.

Owner::fun

There is no distinction between nouns and verbs in variable reference expressions.

2.1.3.3. Creating, reading and updating variables

You can create a new variable, or update the target of an existing variable by assignment. Assignment can be done by = operator of variable references. You can get the target of a variable by dereferencing. Dereferencing can be typically done by local variable dereference expressions or attributional variable dereference expressions.

Note

Variables are never deleted.

For example, the next program does assignment and dereferencing as follows.

  1. Assigns a value to the local variable Window of the context environment.
  2. Creates a variable Theme of a value Window with a string “Dark” as an initial target.
  3. Dereferences the variable and prints out the target. “Dark” is printed out.
  4. Updates the target to a string “Light.”
  5. Dereferences the variable and prints out the target. “Light” is printed out.
# 1
:Window = new_value
# 2
Window::Theme = 'Dark'
# 3
print_line(Window.Theme)  # => Dark
# 4
Window::Theme = 'Light'
# 5
print_line(Window.Theme)  # => Light

Dereferencing of a variable from a value X is performed in the following steps.

  1. The runtime searches the super values of X from nearer to farther, testing each value whether it owns a variable with the specified symbol. If the runtime finds out such a variable, it terminates the search. The variable is said to be the solid variable.
  2. The runtime determines the target of the solid variable as the result of the dereferencing.

If the solid variable is not found on 1, the result of the dereferencing is determined by the following steps.

  1. The runtime calls var_missing function of X, passing a string of the specified symbol as a single argument.
  2. If the result of var_missing is not a list, the runtime throws an exception. If it is, the runtime proceeds to the next step.
  3. The runtime checks the number of elements of the list. If it is 0, the runtime throws an exception.
  4. The runtime gets the first element of the list, which is expected to be the only element, and determines it as the result of the dereferencing.

Look at the program below.

:Window_proto = value
:App_window_proto = Window_proto.new_child
:Browser = App_window_proto.new_child
:Editor = App_window_proto.new_child
:Tooltip = Window_proto.new_child
Window_proto::Theme = 'Dark'
Browser::Theme = 'Light'

# 1
print_line(Browser.Theme)  # => Light
# 2
print_line(Editor.Theme)   # => Dark
# 3
print_line(Tooltip.Theme)  # => Dark

The beginning part of the program constructs these values and variables.

digraph variable_delegation {
graph [size = 4];
node [shape = box];
tooltip [label = "Tooltip"];
editor [label = "Editor"];
browser [label = "Browser"];
app_window_proto [label = "App_window_proto"];
window_proto [label = "Window_proto"];
subgraph cluster_tehems {
    style = "invis";
    dark_theme [label = "'Dark'"];
    light_theme [label = "'Light'"];
}
tooltip -> window_proto [label = "parent", style = "dashed"];
app_window_proto -> window_proto [label = "parent", style = "dashed"];
editor -> app_window_proto [label = "parent", style = "dashed"];
browser -> app_window_proto [label = "parent", style = "dashed"];
window_proto -> dark_theme [label = "variable\n\"Theme\""];
browser -> light_theme [label = "variable\n\"Theme\""];
}

In that case, dereferencing 1, 2 and 3 is done as follows.

  1. Browser owns Theme variable and its target is a string “Light”. Therefore, the result of Browser.Theme is “Light”.
  2. Window_proto is the nearest super value of Editor which owns a variable with the symbol Theme. Hence, the result of Editor.Theme is the string “Dark”, which is the target of Theme variable of Window_proto.
  3. Window_proto is the nearest super value of Tooltip which owns a variable with the symbol Theme. Hence, the result of Tooltip.Theme is the string “Dark”, which is the target of Theme variable of Window_proto.

The next program dereferences No_such_variable variable, which does not exist.

:Owner = new_value
Owner.No_such_variable

When the program is executed, an exception is thrown by the dereferencing and these error message is printed out.

[(stdin):2] Owner.==>No_such_variable
No variable 'No_such_variable' in <value:hash=27219843>

2.1.3.4. var_missing function

When a variable is attempted to dereference, and no super value owns a variable with the specified symbol, var_missing function is called with a receiver and an argument as follows, to provide an alternative value.

Receiver
The value on which the variable is attempted to dereference.
First and the only argument
A string which represents the symbol of the variable.

var_missing function is expected to return an empty list to indicate that there is no alternative value, or a list containing an alternative value. On the former case, the dereferencing fails and it throws an exception. On the latter case, the alternative value shall be the result of the dereferencing.

var_missing function of base delegates the action to extend_base_var_missing function, and falls back to its own default action. Therefore, in most cases, it is better to define extend_base_var_missing instead of var_missing directly.

In this example, extend_base_var_missing finds an alternative value for the map Alternatives. If the alternative value is not found, it returns an empty list to indicate the absence of the alternative value.

use('MAP')

:Value = new_value
Value::Alternatives = MAP.map('Num' 42)
Value::extend_base_var_missing = { [:Me] (:Symbol)
    Me.Alternatives.has_key?(Symbol).then{
        [Me.Alternatives.get(Symbol)]
    }{
        []
    }
}

print_line(Value.Num)  # => 42
print_line(Value.Str)  # => No variable 'Str' in <value:hash=2765>

Note

To simulate Ruby’s “method_missing”, define “extend_base_var_missing” function provide a function which does an alternative action.

2.1.4. Functions

Functions are values which can be called.

2.1.4.1. Creating functions

Functions can be created typically by local function expressions.

In the example below, the target of the variable fun is a function created by local function expressions, which outputs the context receiver and the arguments, and returns the string 'Result'.

:fun = { [:Recv] (:X :Y :Z)
    print_line([Recv X Y Z])
    'Result'
}

:Result = fun['recv'](1 2 3) # => ['recv' 1 2 3]
print_line(Result) # => Result

2.1.4.2. Calling functions

Functions can be called with a receiver, which is a value, and an arguments list, which is a list value. When a function is called, a function call starts. The receiver and the arguments list is said to be passed to the function call.

A function call completes, if it does, in two different ways. It returns a result or throws an exception. The result is a value, and the exception is an exception value.

Functions are called typically by attributional call expressions or local call expressions.

The example below calls a function targetted by a variable named print_line. stdout is the owner of the variable. stderr is the receiver of the function call. Strings “Hello!” and “UTF-8” are elements of the arguments list of the function call.

stdout.print_line[stderr]('Hello!' 'UTF-8')  # => Hello!

In the next example, the actual receiver is omitted. In this case, stdout, the owner of print_line variable, is also passed as the receiver of the function call.

stdout.print_line('Hello!' 'UTF-8')  # => Hello!

When the owner is not explicitly prefixed, the current environment shall be used as the owner. For example, these two lines are equivalent.

print_line('Hello!')       # => Hello!
\env.print_line('Hello!')  # => Hello!

In the next program, head function returns a string of the first two characters of the string 'Hello!'.

print_line('Hello!'.head(2)) # => He

2.1.4.3. Stack frames and Tail calls

Each thread contains a set of stack frames. Each function call may occupy a stack frame until it completes. When stack frames are exhausted and a function call tries to occupy a stack frame, the function call fails to start and the runtime throws an exception.

Tail call is a kind of function call which is guaranteed not to occupy a stack frame.

Tail calls are typically performed by attributional call expressions or local call expressions at the end of function bodies. In the next example, ping function is called in pong function by a tail call, and pong function is called in ping function by a tail call. Those tail calls do not occupy stack frames, hence the program prints out “Ping!” and “Pong!” repeatedly without exceptions caused by exhaustion of stack frames.

:ping = {
    print_line('Ping!')
    pong
}

:pong = {
    print_line('Pong!')
    ping
}

ping
# => Ping!
# => Pong!
# => Ping!
# => Pong!
# => Ping!
# ...

2.1.5. Exceptions

An exception is a break of a normal execution flow. If an exception is thrown, any expressions executed in the current thread are terminated one by one, from the deeper side of the stack up to the shallower, until the exception is caught.

An exception is thrown when something wrong, such as dereferencing of a variable which does not exist, happens. An exception can also be thrown explicitly by a program, using throw function of an exception value.

An exception can be caught by try function. try first calls the function specified as the argument. If the function terminates normally without throwing an exception, try returns a list containing true and the result of the function. If the function throws an exception, try catches it and returns a list containing false and the exception value.

In the next example, try_and_report calls the function given as the argument, and if the function throws an exception, try_and_report prints out “FAILURE: ” and the exception message. If the function terminates normally, try_and_report prints out “SUCCESS: ” and the result.

:try_and_report = { (:fun)
    try($fun).switch(
        regular(:Result) {
            print_line('SUCCESS: ' + Result)
        }
        irregular(:Exception) {
            print_line('FAILURE: ' + Exception.message)
        }
    )
}

try_and_report{ 42 }
# => SUCCESS: 42

try_and_report{ No_such_variable }
# => FAILURE: No variable 'No_such_variable' in <env:#program.name='(stdin)':hash=5718>

try_and_report{ new_exception('Something wrong').throw }
# => FAILURE: Something wrong