1. Characteristics

This chapter introduces characteristics of the language to those who know other languages well.

1.1. Object system without inheritance

There are no classes, nor inheritance. A Kink value is just a bunch of variables.

If you want to make similar values, you can simply write a constructor function like new_dog in the program below.

:new_dog <- {
    'bark' { 'Bow' }
    'howl' { 'Wow' }

:Dog <- new_dog
stdout.print_line(Dog.bark) # => Bow
stdout.print_line(Dog.howl) # => Wow

:Another_dog <- new_dog
Another_dog:howl <- { 'Grrr' }
stdout.print_line(Another_dog.bark) # => Bow
stdout.print_line(Another_dog.howl) # => Grrr

If you don't like a lengthy constructor, or you are concerned with invocation cost of the constructor, you can store pairs of symbols and values in a vector, and use it repetitively.

# Symbol-function pairs of bank accounts
:Account_trait <- [
  # Return the balance of the account
  'balance' {[:A]()

  # Increase the amount to the balance of the account
  'deposit' {[:A](:Amount)
    A:Balance <- A.Balance + Amount

  # Decrease the amount from the balance of the account
  'withdraw' {[:A](:Amount)
    A:Balance <- A.Balance - Amount

# Make an account with the initial balance
:new_account <- {(:Initial_balance)
    ... Account_trait
    'Balance' Initial_balance

:Alice_account <- new_account(100)
stdout.print_line("Balance of Alice's account: {}".format(Alice_account.balance))
# => Balance of Alice's account: 120

:Bob_account <- new_account(300)
stdout.print_line("Balance of Bob's account: {}".format(Bob_account.balance))
# => Balance of Bob's account: 320

The rationale is that inheritance in prototype-based languages can be considered as mere a way of optimization for memory usage by sharing properties among similar objects. Therefore, it is more straightforward to hide the optimization in the runtime implementation, than express it in the language specification.

1.2. No static types

There is no static types for expressions and variables.

Absence of static types can be regarded as a weak point of the language. In exchange for giving up safety by static types, Kink gains the extremely elastic characteristics.

1.3. Function as a first class value

Functions are first class values in a Kink prgram. They can be stored in variables, and passed to other functions as arguments, and so on.

This is a function which doubles a number. The function is stored in a variable named double.

:double <- {(:Num) Num * 2 }

Let's call the function.

stdout.print_line(double(42).repr) # => 84

A function can be passed to other functions.

:Doubles <- [10 20 30].map($double)
stdout.print_line(Doubles.repr) # => [20 40 60]

A function can be called as a method of a value. In the program below, the counter value is passed as the receiver of the method invocation along with the arguments.

# Constructor of counters
:new_counter <- {
    'Num' 0

    # the counter is passed to C
    'num' {[:C]()

    # the counter is passed to C
    'add' {[:C](:Delta)
      C:Num <- C.Num + Delta

# The counter value
:Counter <- new_counter

stdout.print_line(Counter.num.repr) # => 30

1.4. No commas, no semicolons

You don't have to use commas to separate arguments, and you don't have to use semicolons to separate expressions.

In this example, function calls are separated each other.

stdout.print_line('Twift') stdout.print_line('Voxxx')
# Output:
#   Twift
#   Voxxx

In this example, the arguments of of function are separated between each string.

:Yokozuna <- TREE_SET.of('Takanohana' 'Asashoryu' 'Hakuho')
stdout.print_line(Yokozuna.repr) # => Tree_set("Asashoryu" "Hakuho" "Takanohana")

In this example, you have to put the arguments -1 and -2 in parentheses, to prevent the parser from reading the arguments as 0 - 1 - 2.

[0 (-1) (-2)].each{ stdout.print_line(\0.repr) }
# Output:
#   0
#   (-1)
#   (-2)

1.5. Control structures by functions

Control structures are provided by functions.

For example, if-then-else is provided by if_else method of a boolean value.

:Age <- 10
:Fee <- (Age < 12).if_else(
  { :Child_fee = 160
  { :Adult_fee = 310
# => 160

While loop is provided by while_true method of a function value.

:Vec <- ['Foo' 'Bar' 'Baz']
{ Vec.nonempty? }.while_true{
# Output:
#   Foo
#   Bar
#   Baz

1.6. Tail call elimination

Like Scheme and other variants of LISP, the Kink runtime performs tail call elimination. Thus you can implement a loop by a recursive call at the end of a function, without stack overflow.

:count_down <- {(:Num)

  # Invocation of if_true is at the end of the function
  (Num > 0).if_true{
    # Invocation of count_down is at the end of the function
    count_down(Num - 1)

# Output:
#   10000
#   9999
#   9998
#   ,,,
#   2
#   1
#   0

In simple cases you can use methods of vectors or iterators instead of implementing tail recursion. The above example can be written as 10000.down.take_while{ \0 >= 0 }.each{ stdout.print_line(\0.repr) }.

However, tail call elimination is useful for more complex cases, such as implementing a parser.

1.7. Tagged delimited continuation

Kink supports shift and reset functions as operators of tagged delimited continuations. Using those functions, you can save the “rest of calculation under a delimitor” as a function. This function is called a “delimited continuation.” You can call the delimited continuation to restart the calculation.

# paren is assigned to a delimited continuation
:paren <- reset('epokhe'){
  '(' + shift('epokhe'){(:k) $k } + ')'

stdout.print_line(paren('foo')) # => (foo)
stdout.print_line(paren('bar')) # => (bar)

The pair of shift and reset can be a building block for global jumps, generators, coroutines, nondeterministic computation and so on. Kink's exception system is also implemented on shift and reset.