注意 ここにある内容は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 FFI
(C言語のブリッジ)を使った場合はこの保障は崩れますが、その場合も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 ref
がString iso
やString 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がどう嬉しいのかとかよく分かってなかったり解釈に自信がないところが多々あり要修行という感じでした(´・_・`)
現段階だと使い方が分からないものもありますが、consume
やdestructive read
あたりまで頑張るとisoのread/write権を放棄して他のアクターに渡す!とかtrnのwrite権を放棄してvalに変換!とか出来る様になるのでもう一頑張り必要そうです。
そのへんは次回以降に₍₍ (ง´・_・`)ว ⁾⁾