どこか遠くへ行きたい
今年も沢山の知らない言語の情報が流れてきました。
今日はそれらの中から pony をピックアップし体験してみた。
pony
公式サイト : https://www.ponylang.io/
公式チュートリアル : https://tutorial.ponylang.io/
リポジトリ : https://github.com/ponylang/ponyc
Stdlib ドキュメント : https://stdlib.ponylang.org/
Pony is an open-source, object-oriented, actor-model, capabilities-secure, high-performance programming language.
用途 | 汎用 |
開始 | 2012/11 |
最新バージョン | 0.25.0 ( 2018/10/13 ) |
ライセンス | BSD 2-Clause |
プラットフォーム | LLVM |
型 | 静的型付け |
パッケージ管理 | pony-stable |
Githubスター | 3507 ( 2018/11/28 ) |
- オブジェクト指向
- アクターモデルによる並列処理
- コンパイル時に様々な問題を検出 ( capabilities-secure と呼ばれている )
- 型安全
- メモリ安全 ( Null 許さない )
- 例外安全 ( 実行時例外なし )
- データレースなし
- デッドロックなし
- ハイパフォーマンス
- ネイティブコード
- C 呼び出し
Erlang, Scala + Rust みたいな感じだろか。
インストール
・Windows 10 Pro 1803
Github に丁寧な説明がある ので、迷わず進める。
今回は、バイナリを落とした。
$ ponyc.exe -v
# 0.25.0-232a0ab [release]
# compiled with: llvm 3.9.1 -- msvc-15-x64
# Defaults: pic=false ssl=openssl_0.9.0
動いた。
エディタ支援
種類は充実している。が、ほとんどがシンタックスハイライトやコメント入力支援等のごく基本的な物だった。
オートコンプリートやリンタ、シンタックスエラー的な機能は無かった。
Language Server のプロジェクトもあったが、空っぽだった。
サンプル体験
hello world
actor Main
new create(env: Env) =>
env.out.print("Hello, world!")
$ ponyc.exe .
# Building builtin -> C:\ponyc\ponyc-0.25.0-win64\packages\builtin
# Building . -> C:\sample_1204\pony
# Generating
# Reachability
# Selector painting
# Data prototypes
# Data types
# Function prototypes
# Functions
# Descriptors
# Optimising
# Writing .\pony.obj
# Linking .\pony.exe
# Creating library .\pony.lib and object .\pony.exp
$ .\pony.exe
# Hello, world!
動く。
counter
use "collections"
actor Counter
var _count: U32
new create() =>
_count = 0
be increment() =>
_count = _count + 1
be get_and_reset(main: Main) =>
main.display(_count)
_count = 0
actor Main
var _env: Env
new create(env: Env) =>
_env = env
var count: U32 = try env.args(1)?.u32()? else 10 end
var counter = Counter
for i in Range[U32](0, count) do
counter.increment()
end
counter.get_and_reset(this)
be display(result: U32) =>
_env.out.print(result.string())
-
var counter = Counter
でアクターが作られる- 普通のクラスのように簡単に使える
-
?
付きは例外発生の可能性がある?-
try ~ else ~ end
で囲う必要がある?
-
mailbox
ping
と pong
にログ出力をちょっと追加
use "collections"
actor Mailer
be ping(receiver: Main, pass: U32) =>
receiver.display("ping")
for i in Range[U32](0, pass) do
receiver.pong()
end
actor Main
var _env: Env
var _size: U32 = 3
var _pass: U32 = 0
var _pongs: U64 = 0
new create(env: Env) =>
_env = env
try
parse_args()?
start_messaging()
else
usage()
end
be pong() =>
_pongs = _pongs + 1
display("pong" + _pongs.string())
be display(result: String) =>
_env.out.print(result)
fun ref start_messaging() =>
for i in Range[U32](0, _size) do
Mailer.ping(this, _pass)
end
fun ref parse_args() ? =>
_size = _env.args(1)?.u32()?
_pass = _env.args(2)?.u32()?
fun ref usage() =>
_env.out.print(
"""
mailbox OPTIONS
N number of sending actors
M number of messages to pass from each sender to the receiver
"""
)
$ ponyc
# Building builtin -> C:\ponyc\ponyc-0.25.0-win64\packages\builtin
# Building . -> C:\sample_1204\pony
# Building collections -> C:\ponyc\ponyc-0.25.0-win64\packages\collections
# Building ponytest -> C:\ponyc\ponyc-0.25.0-win64\packages\ponytest
# Building time -> C:\ponyc\ponyc-0.25.0-win64\packages\time
# Generating
# Reachability
# Selector painting
# Data prototypes
# Data types
# Function prototypes
# Functions
# Descriptors
# Optimising
# Writing .\pony.obj
# Linking .\pony.exe
$ .\pony.exe 3 3
# ping
# ping
# ping
# pong1
# pong2
# pong3
# pong4
# pong5
# pong6
# pong7
# pong8
# pong9
- アクターには
be
とfun
がある- いわゆるメソッドが
fun
- 同期実行
-
be
は behaviour- 非同期実行
- 戻り値は
None
- いわゆるメソッドが
-
fun
にref
がついている- capability を指定している
-
ref
capability は外すと以下エラーが発生main.pony:39:11: cannot write to a field in a box function. If you are trying to change state in a function use fun ref _size = _env.args(1)?.u32()?
main.pony:40:11: cannot write to a field in a box function. If you are trying to change state in a function use fun ref _pass = _env.args(2)?.u32()?
- デフォルトの capability が
box
(read only) なのでこうなると
capability
多分重要なやつ。
基本的な考え方は、
- アクターはシングルスレッド
- なので、その中の処理でデータ更新が問題になることはない
- Immutable なデータはアクター間で共有されても問題ない
- Mutable なデータを複数アクターで共有し、更新し合うのが問題を引き起こす
- 参照がアクター問わず一つしか無い事 ( isolated と呼ばれる ) が保証されれば、問題は起こらない
ということで、Immutable なデータか isolated なデータの共有だけ行えば並行処理の問題は起こらない というのが方針らしい。
なるほど、アクター間のメッセージのコピーにかかるオーバーヘッドは、適切にメモリアクセスを管理された Shared memory を使うことでなくすことが出来るのか。少し分かってきたぞ。
reference capability
ということで、変数に修飾子として以下を付けることで read/write を制御する。
修飾子 | 参照 (自身) | 他の参照 (自アクター) | 他の参照 (他アクター) | 備考 |
---|---|---|---|---|
iso |
read / write | × | × | |
trn |
read / write | read | × | |
ref |
read / write | read / write | × | |
val |
read | read | read | |
box |
read | read or read/write | read or × | 実態は val or ref ? どちらでも read は保証しているので、この参照で read はできる |
tag |
識別子。アイデンティティ比較に利用 |
見ただけで、何となくは理解したのかな?と思って、実際に書いてみたけど全然書けなかった…
コンパイルエラーしか見てない。
capability の移動やサブタイピング、ジェネリクスなどなど… 考えないといけないことが沢山ある。
今はあまり深みにはまらないようにしよう。
subtyping
↓ trait
trait Red
fun color(): String => "red!!!"
trait Rectangle
fun area(): String => "wide!!!"
↓ 普通に実装
class Cherry is Red
fun say(): String => color()
var cerry = Cherry
env.out.print(cerry.say())
// red!!!
↓ 複数実装
class Kanikama is (Red & Rectangle)
fun say(): String => color() + area()
var kanikama = Kanikama
env.out.print(kanikama.say())
// red!!!wide!!!
// 組み合わせにエイリアスが付けられる (Complex types と言うらしい)
type RedRect is (Red & Rectangle)
class Kanikama is RedRect
Structural subtyping
interface Circle
fun bound(): String
class Ball
fun bound(): String => "boyoyon!!!"
var circle: Circle = Ball
env.out.print(circle.bound())
// boyoyon!!!
表現力
// touple
var x = (I64(1), "one", true)
env.out.print(x._2)
// one
// Union
var y: (String | I64) = if true then 10 else "20" end
match y
| let s: String => env.out.print(s)
| let n: I64 => env.out.print(n.string())
end
// 10
// Intersections
interface box Hashable
fun hash(): USize
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5)] // 構造的にに全部 Hashable の subtype
for v in z.values() do
if v.string() != "1" then
env.out.print(v.hash().string())
end
end
// 9912775131124690402
// 3620018180187439284
凄い
[追記] もしやと思って z: Array[(Hashable & Stringable)]
の型指定を外しても動いた。
型推論で z
は (I64 | String | F64)
と判断されて、それらすべてが hash(): USize
を持ってるので行けるっぽい。
ちなみに、配列に true: Bool
を追加するとそれぞれ違うエラーが出る。
# var z = [I64(1); "2"; F64(3.5); true] の場合
Error:
C:\sample_1204\pony\main.pony:64:24: couldn't find 'hash' in 'Bool'
env.out.print(v.hash().string())
^
Error:
C:\sample_1204\pony\main.pony:64:24: couldn't find hash in Bool val
env.out.print(v.hash().string())
# var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true] の場合
Error:
C:\sample_1204\pony\main.pony:63:69: array element not a subtype of specified array type
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true]
^
Info:
C:\sample_1204\pony\main.pony:63:28: array type: (Hashable box & Stringable box)
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true]
^
C:\sample_1204\pony\main.pony:63:69: element type: Bool val
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true]
^
C:\sample_1204\pony\main.pony:63:69: Bool val is not a subtype of Hashable box: it has no method 'hash'
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true]
^
C:\sample_1204\pony\main.pony:63:69: Bool val is not a subtype of every element of (Hashable box & Stringable box)
var z: Array[(Hashable & Stringable)] = [I64(1); "2"; F64(3.5); true]
エラーも優しかった
パターンマッチ
個人的に重要なので気になる。
fun echo(x: (U32 | String | Foo | (U32, String) | None)): String =>
match x
| 2 => "two" // 値にマッチ
| 3 => "three"
| Foo(5) => "foo 5" // Equatable なインスタンスにマッチ
| None => "none" // 型でマッチ
| let u: U32 => "int " + u.string() // 値をキャプチャして利用可能
| (1, "bob") => "bob is one" // タプルのマッチ
| (2, let name: String) => name + " is two" // タプルで値をキャプチャ
| let s: String if s != "ignored" => s // ガード
else // 何もなければ else に
"no match"
end
echo(2) // "two"
echo(Foo(5)) // "foo 5"
echo(None) // "none"
echo(5) // "int 5"
echo((2, "marley")) // "marley is two"
echo("hoge") // "hoge"
echo("ignored") // "no match"
// Equatable なクラス
class Foo
var _x: U32
new create(x: U32) =>
_x = x
fun eq(that: Foo): Bool =>
_x == that._x
とても直感的で、理不尽にコンパイラに怒られない。
これに capability も条件として付けられるが、コンパイラを納得させることができなかった。
感想
- アクターがカジュアルに使えた
- 表現力高い気がした
- 安全性をどう担保しているのかも少し見えた ( しかし、コードで実感するまでは至れなかった )
- とはいえ、capability と高い表現力、型、アクターが全部絡んだら一筋縄ではいかなそう
- 『使ってみた』とか言って1日で理解できるレベルのもんじゃなかった
Production Ready (独断) | ★★★★☆ |
ドキュメント充実 | ★★★☆☆ |
公式以外の情報 | ★★☆☆☆ |
凄くおもしろそうだったので、機会があったら使ってみたいと思いました。
もっと流行っても良い気がする。
参考
あとがき
※ この記事は個人の見解であり、所属する組織を代表するものではありません。