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{ stdout.print_line(\0.repr) }
# Output:
#   0
#   (-1)
#   (-2)

1.5. 関数による制御構造

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

たとえば、if-then-elseはブーリアン値の if_else メソッドによって実現されます。

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

whileループは関数値の while_true メソッドによって実現されます。

:Vec <- ['Foo' 'Bar' 'Baz']
{ Vec.nonempty? }.while_true{
  stdout.print_line(Vec.pop_first)
}
# Output:
#   Foo
#   Bar
#   Baz

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

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

:count_down <- {(:Num)
  stdout.print_line(Num.repr)

  # 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)
  }
}

count_down(10000)
# Output:
#   10000
#   9999
#   9998
#   ,,,
#   2
#   1
#   0

単純な場合では、末尾呼び出しで独自のループを実装するまでもなく、ベクタやイテレータのメソッドが使えます。たとえば上記の例は、単純に 10000.down.take_while{ \0 >= 0 }.each{ stdout.print_line(\0.repr) } と書けます。

しかしながら、パーサの実装など複雑なケースでは、末尾呼び出しの最適化は有用です。

1.7. タグ付き限定継続

Kinkはタグ付き限定継続を操作する関数として shiftreset をサポートします。これら関数を使って、「区切りまでの残りの計算」を関数として保存できます。この関数を「限定継続」と呼びます。限定継続を呼び出すことで、残りの計算が再開できます。

# 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)

shiftreset のペアは、大域ジャンプ、ジェネレータ、コルーチン、非決定性計算などを構築する部品として使えます。Kinkの例外機構も shiftreset の組み合わせで実装されています。