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 <- {
new_val(
'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]()
A.Balance
}
# 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)
new_val(
... Account_trait
'Balance' Initial_balance
)
}
:Alice_account <- new_account(100)
Alice_account.deposit(20)
stdout.print_line("Balance of Alice's account: {}".format(Alice_account.balance))
# => Balance of Alice's account: 120
:Bob_account <- new_account(300)
Bob_account.deposit(50)
Bob_account.withdraw(30)
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 <- {
new_val(
'Num' 0
# the counter is passed to C
'num' {[:C]()
C.Num
}
# the counter is passed to C
'add' {[:C](:Delta)
C:Num <- C.Num + Delta
}
)
}
# The counter value
:Counter <- new_counter
Counter.add(10)
Counter.add(20)
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.
:TREE_SET.require_from('kink/container/')
: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{(:N) stdout.print_line(N.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
fun of kink/CORE mod.
:Age <- 10
:Fee <- if(Age < 12
{ :Child_fee = 160
Child_fee
}
{ :Adult_fee = 310
Adult_fee
}
)
stdout.print_line(Fee.repr)
# => 160
While loop is provided by while
fun of kink/CONTROL mod.
:CONTROL.require_from('kink/')
:Vec <- ['Foo' 'Bar' 'Baz']
CONTROL.while{ Vec.nonempty? }{
stdout.print_line(Vec.pop_front)
}
# 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)
stdout.print_line(Num.repr)
# Invocation of ``if`` is at the end of the function
if(Num > 0){
# Invocation of count_down is at the end of the function
count_down(Num - 1)
}
}
count_down(10000)
# 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{(:N) N >= 0 }.each{(:N) stdout.print_line(N.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.
:KONT.require_from('kink/')
# paren is assigned to a delimited continuation
:paren <- KONT.reset('epokhe'){
'(' + KONT.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
.