1. 特徴

この章では、他の言語を良く知っている人向けに、 Kink の特徴を紹介します。

1.1. 関数的

Kink は擬似関数的言語です。「関数的」とはつまり、関数が第一級の値だということです。関数は変数に格納でき、また他の関数に引数として渡すこともできます。

次の例は、数を二倍する関数です。この関数を、 double という名前の変数に格納しています。

:double = { (:Num) Num * 2 }

関数を呼んでみましょう。

print_line(double(42)) # => 84

この関数を他の関数に渡してみましょう。

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

「擬似」とは、 Kink のプログラムで変数に値が複数回格納でき、したがって純関数的でないことを表しています。言語が純関数的でないとしても、プログラムを関数的なやり方で書くのは良い習慣です。

1.2. プロトタイプベース

Kink のオブジェクトシステムは、クラス無しで継承を実現しています。値は親から変数を継承します。この種のオブジェクトシステムは「プロトタイプベース」と呼ばれます。

次の例では、 bark 関数と howl 関数を持つ Dog という値を、クラス無しで作っています。

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

Dog の変種を作るには、子供を作ります。子供は親から変数を継承します。

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

プロトタイプベースの継承は、次に例を挙げるように、色々な用途に使われます。

  • プロトタイプと普通の値の関係 (上述)
  • 基底型と派生型の関係
  • ローカル変数のレキシカルスコーピング

1.3. JVM 上での動作

Kink は JVM 上で動きます。したがって、 Kink プログラムから Swing の様な Java クラスライブラリが使えます。

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

既存のインタフェースやクラスを拡張した、新しいクラスのインスタンスを作ることもできます。

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

1.4. カンマ要らず、セミコロン要らず

引数や四季を区切るのに、カンマやセミコロンを使う必要はありません。

次の例では、関数呼び出しが正しく区切られています。

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

次の例では、 Set.set 関数の引数が文字列ごとに区切られています。

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

次の例では、二つ目の引数である -10 を括弧に入れる必要があります。これは、パーサが 0 - 10 として解釈することを防ぐためです。

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

1.5. 関数による制御構造

制御構造は関数によって実現されます。

たとえば、 while ループは関数値の loop 関数によって実現されます。

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

If-then-else はブーリアン値の then 関数によって実現されます。

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

1.6. 関数呼び出しによる代入

Kink の演算子は、関数呼び出しなど他の式の構文糖です。代入も例外ではありません。 :Num = 42 は単に :Num.op_set(42) と解釈され、変数参照 :Numop_set 関数が呼び出されます。

= 演算子を使って複数の変数に同時に値を代入することもできます。これは、変数参照を要素に持つリストの op_set 関数を呼び出すことで行います。

: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

インタプリタは、よく使われる種類の代入を最適化します。しかし、最適化は透過的に行われるため、プログラマが気にする必要はありません。

1.7. 末尾呼び出しの最適化

Scheme その他の LISP 変種と同じように、 Kink のインタプリタは末尾呼び出しの最適化を行います。したがって、ループは関数末尾での再起呼び出しとして実装できます。スタック溢れを起こす心配はありません。

:count_down = { (:Num)
    print_line(Num)
    (Num > 0).then{ count_down(Num - 1) }
}
count_down(100)
# => 100
# => 98
# => 97
# ...
# => 2
# => 1
# => 0

多くの場合、末尾呼び出しで独自のループを実装するまでもなく、リストやシーケンスの関数が使えます。たとえば上記の例は、単純に 100.down.while{ \0 >= 0 }.loop($print_line) と書けます。

しかしながら、ステートマシンの実装のように、より複雑な制御が必要な場合には、末尾呼び出しの最適化が有用です。たとえば、入力の各行から、 “#” 以降をコメントとして削除するフィルタを考えてみてください。このフィルタは次のようなステートマシンとして表せます。

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"]
}

このステートマシンは、それぞれの状態を関数に、状態遷移を関数呼び出しに、そのまま割り当てることで実装できます。入力文字列が長かったとしても、スタックが溢れることはありません。なぜなら、 initial 関数と in_comment 関数の呼び出しは末尾呼び出しであり、スタックを余分に積まないからです。

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