Ponylangで型付きアクター生活[4] Capabilities 編

注意 ここにある内容はpony2.x時代にドキュメントに書かれていた物なので、もう古い可能性が高いです。最新の情報は公式ドキュメントを参照してください。

追記: 2016/1/5 試してもらった人によると以下を実行する必要があったとのことです。(環境によりそうなので困ったら試すと解決するかもしれません)

export DYLD_LIBRARY_PATH=/usr/local/opt/llvm/lib:$DYLD_LIBRARY_PATH

== 本文

はろぽにー₍₍ (ง´・_・`)ว ⁾⁾

今回は待ちに待ったCapabilities編です。

Ponyでのcapabilities-secureというのは

  • 型安全
  • メモリセーフ
  • 例外安全
  • データ競合フリー
  • デッドロックフリー のことを指しています。

今回はこの辺りがどのように実現しているのかについて見ていきます。

Object capabilities

公式ドキュメントだとこの辺です。 http://tutorial.ponylang.org/capabilities/object-capabilities/

Ponyのcapabilities-secureな型システムはobject-capabilityモデルに基づいたものです。 object-capabilityってなんやって気になりますが、コアのアイデアは以下のようなものらしいです。

A capability is an unforgeable token that (a) designates an object and (b) gives the program the authority to perform a specific set of actions on that object.

capabilityというのは、unforgeable(後述)なトークンのことです。 このトークンはオブジェクトを指し示していて、それを持っているとある操作の集合の実行権限をプログラムに与えるものです。 トークンというのはアドレスとかポインタとか参照のようなもののことです。

unforgeable(ねつ造不可能)って?

Ponyはポインタ演算を持たず、型安全かつメモリ安全な言語なので、オブジェクトの参照を(あえて、ねつ造しようと思っても)作り出すことができません。オブジェクトの参照を得るには、オブジェクトを生成するか、他のオブジェクトから参照を渡される必要があります。 このように入手方法が限定されているためPonyのトークンはunforgeableだということが出来るらしいです。

なおC FFIC言語のブリッジ)を使った場合はこの保障は崩れますが、その場合もC FFI trust boundaryという概念で保障が崩れる範囲を制御することができます。

object-capabilityは何の役に立つの?

オブジェクトのパーミッションリストやアクセスコントロールリストなどのセキュリティ機構を持たせる代わりに、object-capabilityモデルを使うことができます。 そうすると、単にオブジェクトの参照を持っていればそのオブジェクトである操作を行うことができるということが保証されるようです。 その辺りの詳細はCapability Myths Demolishedを参照してくださいとのことです。

Reference capabilites

reference capabilitesの説明では、UNIXのファイル操作が例に挙げられています。

int fd = open("/etc/passwd", O_RDWR);

上記のようにファイルをオープンするとファイルディスクリプタが得られます。これがトークンになります。Ponyではこのトークンがunforgeableなんでしたね。

ファイルを開くときは、それと同時にリードパーミッションやライトパーミッションを得るのが一般的です。Ponyではこのファイル操作時のパーミッションのようなものを全ての参照が持つようになっています。このパーミッションのようなものがreference capabilityです。

Ponyでの参照は常にreference capabilityを持ちます。というよりreference capabilityの一部になっています。このパーミッションが、他のアクターと変数を共有した状態で並行処理を行ってもコンパイラが安全性をチェックできるようにしてくれます。

Basic Concepts

reference capabilityの根本となる考え方は以下にまとめられます。

  • mutableなデータを共有するのは困難

  • immutableなデータの共有は簡単

  • isolatedなデータは安全

    • 参照を一つしか持たないデータのことをisolatedなデータと呼びます。
    • 複数のスレッドに渡されたりしても、同時に参照を持っているのが一つのスレッドであれば、isolatedだと言えます。
  • isolatedなデータは複雑(かも)

    • 色々な参照をもった巨大なデータ構造だと、どこまでがisolatedなデータなのか把握するのが難しくなります。
    • Ponyではデータがisolatedかどうかの把握に、isolation boundaryという考え方を用います。以下の様なboundaryに関するルールを満たしていれば、そのデータはisolatedです。
      • boundary内のオブジェクトを指すboundary外からの参照は一つだけである。
      • boundary内での参照はいくらでもあってもよいが、それらがboundary外のオブジェクトを指してはならない。
  • 全てのアクターはシングルスレッド

immutableなデータの共有と、isolatedなデータの交換だけでプログラムを組めばロック無しで並行処理を安全に行うことが出来ます。 と、言うのは簡単なんですが、immutableなはずのデータを変えてしまったりisolatedなデータの参照を不用意に渡して共有してしまうとすぐに安全でなくなってしまいます。Ponyではこれらがコンパイラによってチェックされるので安心!!というわけですね。

型修飾子

PonyではC/C++constのような型修飾子をたくさん使います。 どのくらいたくさん使うかというと変数に型をつけるところでは常に使います。

class Wombatのような型の宣言の時にはつかいませんが、Wombat型の変数を使うときには必要です。 StringでいうとString refとするとmutableになり、String valとするとimmutableになります。

reference capabilityの種類一覧

Ponyでのreference capabilitiesは六種類です。それぞれについては今後細かく解説されるそうなので、今はざっくりです。

カッコ内はコードで書くときの名称です。

Isolated(iso) isolatedなデータ構造に対する参照です。isoな変数では、そのデータへアクセスできる他の変数が存在しないことが保証されます。なので、好きなように変更したり他のアクターへ渡したりすることができます。

Value(val) immutableなデータ構造に対する参照です。valな変数では誰もその値を変更することができないことが保証されます。なので、値を好きなように読んだりしたり他のアクターと共有したりすることが出来ます。

Reference(ref) mutable(でisolatedではないよう)なデータ構造に対する参照です。言い換えると普通のデータに対する参照です。refな変数では値を読んだり書いたり、複数の変数から同一のデータを参照したりすることが出来ます。しかし他のアクターと共有することは出来ません。

Box. その変数にとってはread-onlyなデータです。このデータは「immutableで他のアクターと共有されている」か「自分のアクター内で、そのデータを変更できるような参照を持つ他の変数が存在している」というような状況を表しています。どちらにせよbox変数があればデータを安全に読むことが出来ます。他の変数は知らないけど自分は読み込みしかしないよということですね。自分は変えないけど他の人が値を変えるかもしれないということを表現できるのでrefなどと一緒につかうことができます。またどのみち変更されないvalのような変数と一緒に使うことも出来ます。逆にisoとは一緒に使うことが出来ません。isoなデータでは読み込みも書き込みもその変数しか行わないことが保証されるからです。

Transition(trn) trnな変数では、データに値を書き込みつつread-only(box)な参照を他の変数に提供することができます。trnな変数を後にvalにすることもできます。(ちょっと自信なし)

Tag tagは一意性のためにのみ使われます。データを読んだりすることはできません。オブジェクトの同一性を調べたりするために使います。他のアクターと共有することが出来ます。

reference capabilityの書き方

こんな感じで書きます

String iso // An isolated string
String trn // A transition string
String ref // A string reference
String val // A string value
String box // A string box
String tag // A string tag

また、class String valのように型宣言時に型修飾子をつけるとデフォルトのreference capabilityをつけることが出来ます。 これにより、単にStringを書くと自動的にString valと書いたのと同じ意味になります。

型宣言時のデフォルト指定は必須ではありません。 書かない場合はclassではref, primitiveではval, actorではtagのように、型の種類によって自動的に決めてくれます。

書いてみよう

今回は簡単にためせそうなvalやisoをとりあえず書いてみました。 Pony Programming Language Sandbox で試すことが出来ます。 recover String() end は今後出てくる文法です。今はこうするとString refString isoString valに変換されるということだけ把握して読み飛ばしておいてください。

actor Main
  new create(env: Env) =>
// isoの実験
    var i: String iso = recover String() end
    i.append("test")
    
       // コンパイルエラー
      // var a: String iso  = i    
      // var b: String box = i
      // var c: String val = i

// valの実験
    var v: String val = recover String() end
    // コンパイルエラー
    //    v.append("test2")

終わり

疲れたけど、一番気になっていたところが分かったので良かったです。 とはいえobject-capabilityがどう嬉しいのかとかよく分かってなかったり解釈に自信がないところが多々あり要修行という感じでした(´・_・`)

現段階だと使い方が分からないものもありますが、consumedestructive readあたりまで頑張るとisoのread/write権を放棄して他のアクターに渡す!とかtrnのwrite権を放棄してvalに変換!とか出来る様になるのでもう一頑張り必要そうです。

そのへんは次回以降に₍₍ (ง´・_・`)ว ⁾⁾