Ponyとは
皆さんPonyという言語はご存知でしょうか? 簡単に言うと「Actorモデルを採用した並行・並列処理を行うことができる静的型付けオブジェクト指向言語」です。さらに、capabilities-secureと呼ばれるコンパイル時に型だけでなく並行処理に関するエラーなどを検知する機能が豊富に備わっている言語です。その中でも初歩の概念である、reference capabilityについてクロージャを通して簡単に紹介してみたいと思います。
クロージャを利用したカウンター
クロージャの例としてよく挙げられるカウンターの実装です。Ponyのreference capabilityの威力がわかりやすいようにいくつかの言語で実装をしてみました。
いずれの言語も以下のような結果が出力される想定です。
1
2
3
4
5
6
7
8
9
10
JavaScript
const createCounter = () => {
let count = 0;
return () => {
count += 1;
return count;
}
}
const counter = createCounter();
Array.from({length: 10}, () =>
console.log(counter())
)
Java
import java.util.function.*;
import java.util.stream.IntStream;
public class Counter {
public static void main(String ...args) {
final Supplier<IntSupplier> createCounter = () -> {
// countはfinalのため配列を利用して値を書き換える。
final Integer[] count = {0};
return () -> {
count[0]++;
return count[0];
};
};
IntSupplier counter = createCounter.get();
IntStream.rangeClosed(1, 10).forEach(i -> {
System.out.println(counter.getAsInt());
});
}
}
Scala
object Counter extends App {
val createCounter = () => {
var count = 0
() => {
count += 1
count
}
}
val counter = createCounter()
(1 to 10).foreach { _ =>
println(counter())
}
}
JavaScriptとScalaによる実装は考え方が似ています。どのような点で似ているかというと、無名関数が自由変数を取り込み、クロージャとなり、自由変数の値を書き換えることができるという点です。
一方 Javaは、実質的にfinal
で宣言された変数(※1)しか自由変数として取り込めないため、プリミティブな値ではなく、配列などのミュータブルなオブジェクトを取り込み参照先を書き換える事でしか値を変更することができません。これはクロージャとしては、安全で正しい振る舞いではありますが、利便性や可読性の点からすると厳しいものがあります。
Ponyによるカウンター
それでは Ponyは、どのようなアプローチで上記に上げた言語の問題を解決しているのでしょうか? コード中に現れる、count
という変数に注目してみましょう。Ponyは標準ではラムダ式の外から取り込んだ自由変数の値を書き換える事が出来ません。この点では、Javaと同じ動きをします。もし、変数の値を書き換えたいときは、ref
キーワードをラムダ式に付与します。そうすることで、ラムダ式自体が中で変数の書き換えを行うことを許可します。また、count
を更新しているラムダ式の外では、count
は、0のままになります。本当に更新したい場合にはJavaと同じように配列などに入れる等のアプローチが必要ですが、そのようなケースは少ないと思われるので非常に安全なコードを書けます。
use "collections"
actor Main
new create(env:Env) =>
let createCounter = {(): {ref(): U32} =>
var count = U32(0)
// refキーワードを利用することで、countの値を書き換えることが可能になる。
{ref()(count): U32 =>
count = count + 1
count
}
}
let counter = createCounter()
for i in Range[U32](0, 10) do
env.out.print(counter().string())
end
ref
キーワードをを取り除いてコンパイルした場合は、以下のようなエラーがでます。とてもわかり易いですね。
Error:
main.pony:9:17: cannot write to a field in a box function. If you are trying to change state in a function use fun ref
count = count + 1
まとめ
非常に簡単な例ではありますが、クロージャを通してcapabilities-secureなコードの威力が伝わったでしょうか? 言語仕様により、書き換えが可能・不可能かを一本化するのではなく、モードの切り替えで安全にコードが書けるのは魅力的だと思います。これは、Ponyのcapabilities-secureの魅力のホンの一端でしかありません。是非、一緒にPonyの魅力に触れていきましょう!
補足
※1 Javaでは、自由変数の再代入が無ければfinal
キーワードを避けることはできます。しかし、今回の用途では必ず再代入が発生するため、実質的にfinal
キーワードは必須です。