20
9

More than 5 years have passed since last update.

知らない言語を使ってみたい - pony 編 (Erlang,Scala(Akka)+Rust みたいな)

Last updated at Posted at 2018-12-02

どこか遠くへ行きたい

今年も沢山の知らない言語の情報が流れてきました。
今日はそれらの中から 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

pingpong にログ出力をちょっと追加

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
  • アクターには befun がある
    • いわゆるメソッドが fun
      • 同期実行
    • bebehaviour
      • 非同期実行
      • 戻り値は None
  • funref がついている
    • 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 (独断) ★★★★☆
ドキュメント充実 ★★★☆☆
公式以外の情報 ★★☆☆☆

凄くおもしろそうだったので、機会があったら使ってみたいと思いました。
もっと流行っても良い気がする。

参考

あとがき

※ この記事は個人の見解であり、所属する組織を代表するものではありません。

20
9
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
20
9