注意 ここにある内容はpony2.x時代にドキュメントに書かれていた物なので、もう古い可能性が高いです。最新の情報は公式ドキュメントを参照してください。
はろぽに〜₍₍ (ง´・_・`)ว ⁾⁾
前回iso
とtrn
はそのままでは代入できないということが判明したので今回はその辺を便利に(?)使う方法を見ていきます。
本家だとこの辺→http://tutorial.ponylang.org/capabilities/consume-and-destructive-read/です。
Consuming a variable
オブジェクトの参照を他の変数に移動させることができます。
consume
を使うと、ある変数の参照を無効にして、他の変数に参照を移動させることができます。
fun test(a: Wombat iso) => var b: Wombat iso = consume a // Allowed!
上記のコードだとa
はもう使うことができなくなります。
fun test(a: Wombat iso) => var b: Wombat iso = consume a // Allowed! var c: Wombat tag = a // Not allowed!
destructive read
isoなフィールドをconsumeすると、フィールドの値が空っぽになってしまい、オブジェクトの状態が不正になってしまいます。 そのため、destructive readという仕組みを使う必要があります。
class Aardvark var buddy: Wombat iso fun test(a: Wombat iso) => var b: Wombat iso = buddy = consume a // Allowed!
ponyの代入演算子は代入後の新しい値ではなく代入前の古い値が返されることを思い出すと、
上記のコードはa
がconsumeされた後に、buddy
に代入される。buddy
に入っていた値はb
に入ります。
結果的にisoなフィールドであるbuddy
の参照を他の変数であるb
で受け取ることができました。
buddyの値が空っぽになると同時にconsume a
で、a
の値が入るので安全という仕組みになっています。
Recovering capabilities
Ponyではreference capabilityを lift することができます。
例えばmutable reference capability(iso
, trn
, ref
)はどんなreference capabilityにもなれます。
またimmutable reference capability(val
, box
)はimmutableかopaque reference capability(tag
)になれます。
つまりローカル変数ではmutableとして変数作っておいて色々操作をして値を組み立てた後、返り値としてはimmutableな参照として返すといったことができる仕組みです。
文法としては、recover Array[String] end
のように使います。これはArray[String] ref
の代わりにArray[String] iso
を返します。
標準ライブラリのToStringの実装では以下のように使われています。
recover var s = String((prec' + 1).max(width.max(31))) var value = x try if value == 0 then s.push(table(0)) else while value != 0 do let index = (value = value / base) - (value * base) s.push(table(index)) end end end s.append(typestring) _extend_digits(s, prec') s.append(prestring) _pad(s, width, align, fill) s end
まずString ref
を作って色々な操作をした後にString iso
として参照を返しています。
注意点
recover
の中でもrecoverの外側の変数にアクセスすることが出来ますが、sendableな値(他のアクターへ送れるiso
やval
, tag
など)しか参照できません。これが安全にliftを行うための条件です。(ref
とかで参照をコピーしちゃえば他でも使えてしまったりする危険性があるのでコンパイルエラーになります)
Automatic receiver recovery
メソッドレシーバーがiso
やtrn
だと、ref
なメソッドを呼ぶことが出来ません。メソッドを呼ぶ側はレシーバーへの参照を持っていますし、メソッドを処理するところでは、自分自身への参照を持っているからです。なのでiso
だったらtag
, trn
だったらbox
などのようにaliasを行う必要があります。
しかし全ての引数と返り値の型がsendableであれば自動的に変換を行ってくれるので上記のaliasは必要なくなります。
下に例を示します。
let s = recover String end s.append("hi")
sはString iso
であり、それに対してappendを行っています。appendはref
なメソッドレシーバーを要求するので通常はiso
に対して呼ぶことは出来ませんが、automatic receiver recoveryによって呼ぶことができています。
Combining capabilities
オブジェクトにはreference capabilitiesがあり、そのフィールドにもreference capabilitiesがあるので、最終的にどのように見えるのかはこの2つの要素(origin, field)の組み合わせが重要になってきます。
Viewpoint adaptation
この組み合わせのことをviewpoint adaptation
と呼びます。
▷ | iso field | trn field | ref field | val field | box field | tag field |
---|---|---|---|---|---|---|
iso origin | iso | tag | tag | val | tag | tag |
trn origin | iso | trn | box | val | box | tag |
ref origin | iso | trn | ref | val | box | tag |
val origin | val | val | val | val | val | tag |
box origin | tag | box | box | val | box | tag |
tag origin | n/a | n/a | n/a | n/a | n/a | n/a |
例として、trnなoriginからrefなフィールドにアクセスするとboxになります。
class Foo var x: String ref class Bar fun f() => var y: Foo trn = getTrnFoo() var z: String box = y.x
iso
なorigin/fieldのときに参照はisoになりますが、単純にaliasingすることができないのでread/writeユニークが保たれます。
例えば、
class Foo var a: String iso = recover String().append("test") end new create() => "foo"
があったとき
actor Main new create(env: Env) => var foo: Foo iso = recover Foo.create() end var f1: String iso = foo.a // コンパイルエラー(foo.aはiso!) var f2: String iso = foo.a = recover String() end // コンパイルを通る(destructive readを使う)
のようになります。
Passing and sharing
mutableなデータは他のアクターに 渡す ことができます。またimmutableなデータは他のアクターと 共有 することができます。 しかもロックやコピーなどをしなくても安全です。
Passing
mutableなデータ(iso
, trn
, ref
)は他のアクターからreadすることはできません。
その参照が存在する限り値が書き換わってしまうかもしれないからです。
しかし、値が書き換わってしまうような参照を放棄すれば他のアクターへ安全に渡すことが出来ます。
isoな参照をconsume a
を使って他のアクターへ渡すことで上記のような安全なデータの渡し方が実現できます。
またtrnでもvalに変換すれば同じように他のアクターへ安全に渡すことができます。(trn自体は渡せないことに注意)
他のアクターへ渡す予定がない変数はiso
ではなくref
を使ってもよいです。渡す予定がある場合はiso
を使いましょう。
Sharing
他のアクターと値を共有するためには完全に値をimmutableにする必要があります。つまりval
を使う必要があります。box
は他に変えるかもしれない参照がいるので渡すことが出来ません。
他のアクターへ渡す予定がある場合はval
を使いましょう。ない場合はbox
などを使っても構いません。
またtag
も共有することができます。readが出来ないのでいくら書き換わっても安全だからです。
Reference capabilities that can't be sent
ref
, box
, trn
は値を渡すことが出来ません。trn
は以下のようにval
に変換することができるので、全く渡せない訳ではありませんが、trn
自体は渡せないことに注意してください。
actor Main new create(env: Env) => var foo = Foo.create(env) var s: String trn = recover String().append("test") end foo.foo(consume s) // trnをconsume actor Foo var env: Env new create(env': Env) => env = env' be foo(f: String val) => // valで受け取る env.out.print(consume f)
終わりに
consumeが使えるようになったのでだいぶいい感じにPonyでコードを書けるようになってきました。 次回はreference capabilitiesのサブタイプ関係などについて見ていきます₍₍ (ง´・_・`)ว ⁾⁾