1. Characteristics

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

1.1. Functional

Kink is a pseudo-functional language. “Functional” means that functions are first class values. They can be stored in variables, and passed to other functions as arguments.

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.

print_line(double(42)) # => 84

The function can be passed to other functions.

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

“Pseudo” means that the language is not purely functional, since variables can be set multiple times in a Kink program. Although the language is not purely functional, writing programs in a functional way is a good habit.

1.2. Prototype-based

The object system of Kink supports inheritance without classes. A value inherits variables from its parent. This type of object system is called “prototype-based.”

You can make Dog value which has bark function and howl function without making a class.

:Dog = new_value('bark' { 'Bow' } 'howl' { 'Wow' })
print_line(Dog.bark) # => Bow
print_line(Dog.howl) # => Wow

You can make a variant of Dog by making a child. A child inherits its variables from the parent.

:Puppy = Dog.new_child
Puppy::bark = { 'Yelp' }
print_line(Puppy.bark) # => Yelp
print_line(Puppy.howl) # => Wow

Prototype-based inheritance is used in various ways, such as:

  • Relationship between a prototype and regular values (shown above)
  • Relationship between a supertype and subtypes
  • Lexical scoping of local variables

1.3. Runs on JVM

Kink runs on JVM. Thus, you can use Java class libraries, such as Swing, in a Kink program.

use('Java.null')
use('javax.swing.JOptionPane')
JOptionPane.showMessageDialog(null 'This is a Swing message dialog!')

You can also make a Java instance of a new class, extending an interface or a class.

use('java.lang.Thread')
:Tick_thread = Thread.new{
    { true }.loop{
        print_line('Tick!')
        Thread.sleep(1000)
    }
}
Tick_thread.start
# => Tick!
# => Tick!
# => Tick!
# ...

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.

print_line('Twift') print_line('Voxxx')
# => Twift
# => Voxxx

In this example, the arguments of “Set.set” function are separated between each string.

use('SET')
:Yokozuna = SET.set('Takanohana' 'Asashoryu' 'Hakuho')
print_line(Yokozuna) # => set('Asashoryu' 'Takanohana' 'Hakuho')

In this example, you have to put the second argument -10 in parentheses, to prevent the parser from reading the arguments as 0 - 10.

use('java.lang.Math')
print_line(Math.atan2(0 (-10))) # => 3.141592...

1.5. Control structures by functions

Control structures are provided by functions.

For example, while loop is provided by loop function of a function value.

:List = ['Twift' 'Voxxx' 'Olv']
{ List.any? }.loop{
    print_line(List.pop)
}
# => Olv
# => Voxxx
# => Twift

If-then-else is provided by then function of a boolean value.

:Fee = (Age < 12).then{ Child_fee }{ Adult_fee }

1.6. Assignment by function calls

In Kink, operators are syntax sugar for other expressions, such as function calls. Assignment is no exception. :Num = 42 is simply read as :Num.op_set(42), hence it calls op_set function of a variable reference :Num.

You can set multiple variables at once using = operator. It is performed by op_set function of a list which contains variable references.

:Line = "127.0.0.1\tlocalhost"
[:Ip_address :Host_name] = Line / "\t"
print_line(Ip_address)  # => 127.0.0.1
print_line(Host_name)   # => localhost

The interpreter performs special optimization for common types of assignment, but you don’t have to mind it because it is done transparently.

1.7. Tail call elimination

Like Scheme and other variants of LISP, the Kink interpreter does 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)
    print_line(Num)
    (Num > 0).then{ count_down(Num - 1) }
}
count_down(100)
# => 100
# => 98
# => 97
# ...
# => 2
# => 1
# => 0

In most cases you can use functions of lists or sequences instead of implementing tail recursion. The above example can be written simply as 100.down.while{ \0 >= 0 }.loop($print_line).

However, tail call elimination is useful for more complex cases, such as a state machine. For example, think about a filter which eliminates characters after “#” sign as a comment on each line. It can be represented as the following state machine.

digraph state_machine {
initial -> initial [label = "Except for \"#\""]
initial -> in_comment [label = "\"#\""]
initial -> final [label = "End of String"]
in_comment -> initial [label = "\"\\n\""]
in_comment -> in_comment [label = "Except for \"\\n\""]
in_comment -> final [label = "End of String"]
}

The state machine can be implemented straightforwardly assigning each state as a function, and each state transition as a function call. This program never cause a stack overflow even if the input string is long, because each call of initial and in_comment is a tail call, thus it does not lengthen the call stack.

use('REGEX.regex')

:initial = { (:Str)
    Str.switch(
        regex('#(?<Rest>.*)').capture(\env) {
            in_comment(Rest)
        }
        regex('(?<Char>.)(?<Rest>.*)').capture(\env) {
            print(Char)
            initial(Rest)
        }
        any {}
    )
}

:in_comment = { (:Str)
    Str.switch(
        regex('\n.*') {
            initial(Str)
        }
        regex('.(?<Rest>.*)').capture(\env) {
            in_comment(Rest)
        }
        any {}
    )
}

:Input = 'Takanohana # 65th
Asashoryu # 68th
Hakuho # 69th
'
initial(Input)
# => Takanohana
# => Asashoryu
# => Hakuho