1. 特徴¶
この章では、他の言語を良く知っている人向けに、 Kink の特徴を紹介します。
1.1. 継承なしのオブジェクトシステム¶
クラスも継承も存在しません。Kinkの値は単に変数のまとまりです。
同じような値が作りたければ、そのようなコンストラクタ関数を書けばよいのです。たとえば次のプログラムの new_dog
関数のようにです。
: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
コンストラクタが長ったらしくなることや、呼び出しコストが気になる場合は、シンボルと値の一覧をベクタとして取っておいて、繰り返し使うこともできます。
# 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
理論的根拠は次のとおりです。プロトタイプベース言語における継承は、単に同種のオブジェクト間で属性を共有することにより、メモリ使用量を最適化する手法と考えられます。そうであれば、継承を言語仕様の中に表出させるよりも、ランタイムの実装の中に最適化として押し込めるほうが素直です。
1.2. 静的型の欠如¶
式や変数に静的型はありません。
静的型が欠如していることは、言語にとっての弱点と考えられます。静的型がもたらす安全性をあきらめる引き換えとして、Kinkはきわめて柔軟であるという特徴を得ています。
1.3. 第一級の値としての関数¶
Kinkプログラムにおいて、関数は第一級の値です。関数は変数に格納でき、また他の関数に引数として渡すこともできます。
次の例は、数を二倍する関数です。この関数を、 double
という名前の変数に格納しています。
:double <- {(:Num) Num * 2 }
関数を呼んでみましょう。
stdout.print_line(double(42).repr) # => 84
関数は他の関数に渡せます。
:Doubles <- [10 20 30].map($double)
stdout.print_line(Doubles.repr) # => [20 40 60]
関数は値のメソッドとして呼び出せます。次のプログラムでは、カウンタ値がメソッド呼び出しのレシーバとして、実引数と一緒に渡されています。
# 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. カンマ要らず、セミコロン要らず¶
引数や式を区切るのに、カンマやセミコロンを使う必要はありません。
次の例では、関数呼び出しが正しく区切られています。
stdout.print_line('Twift') stdout.print_line('Voxxx')
# Output:
# Twift
# Voxxx
次の例では、 of
関数の引数が文字列ごとに区切られています。
:TREE_SET.require_from('kink/container/')
:Yokozuna <- TREE_SET.of('Takanohana' 'Asashoryu' 'Hakuho')
stdout.print_line(Yokozuna.repr) # => Tree_set("Asashoryu" "Hakuho" "Takanohana")
次の例では、引数である -1
と -2
を括弧に入れる必要があります。これは、パーサが 0 - 1 - 2
として解釈することを防ぐためです。
[0 (-1) (-2)].each{(:N) stdout.print_line(N.repr) }
# Output:
# 0
# (-1)
# (-2)
1.5. 関数による制御構造¶
制御構造は関数によって実現されます。
たとえば、if-then-elseは kink/CORE モジュールの if
関数によって実現されます。
:Age <- 10
:Fee <- if(Age < 12
{ :Child_fee = 160
Child_fee
}
{ :Adult_fee = 310
Adult_fee
}
)
stdout.print_line(Fee.repr)
# => 160
whileループは kink/CONTROL モジュールの while
関数によって実現されます。
:CONTROL.require_from('kink/')
:Vec <- ['Foo' 'Bar' 'Baz']
CONTROL.while{ Vec.nonempty? }{
stdout.print_line(Vec.pop_front)
}
# Output:
# Foo
# Bar
# Baz
1.6. 末尾呼び出しの最適化¶
Schemeその他のLISP変種と同じように、Kinkの処理系は末尾呼び出しの最適化を行います。したがって、ループは関数末尾での再起呼び出しとして実装できます。スタック溢れを起こす心配はありません。
: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
単純な場合では、末尾呼び出しで独自のループを実装するまでもなく、ベクタやイテレータのメソッドが使えます。たとえば上記の例は、単純に 10000.down.take_while{(:N) N >= 0 }.each{(:N) stdout.print_line(N.repr) }
と書けます。
しかしながら、パーサの実装など複雑なケースでは、末尾呼び出しの最適化は有用です。
1.7. タグ付き限定継続¶
Kinkはタグ付き限定継続を操作する関数として shift
と reset
をサポートします。これら関数を使って、「区切りまでの残りの計算」を関数として保存できます。この関数を「限定継続」と呼びます。限定継続を呼び出すことで、残りの計算が再開できます。
: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)
shift
と reset
のペアは、大域ジャンプ、ジェネレータ、コルーチン、非決定性計算などを構築する部品として使えます。Kinkの例外機構も shift
と reset
の組み合わせで実装されています。