uehaj's blog

Grな日々 - GroovyとかGrailsとかElmとかRustとかHaskellとかReactとかFregeとかJavaとか -

「プログラムでシダを描画する」をelmで描画する

やや乗り遅れているネタとして、シダを描くというのを、elm言語でやってみました。
(追記: 改良版も作りました)

elm言語は、基本はHaskellライク文法(サブセット方向)に、F#とOCaml風味の演算子・文法を振り掛けた、ヒンドリーミルナー型推論・純粋関数型・正格評価の言語で、repl上もしくは主にJSにコンパイルしてブラウザ内で実行します*1。特徴はFRP,ファンクショナルリアクティブプログラミングをサポートする言語だということです。

以下がシダを描画するelmコード。もっといい書き方あると思うので気付いたらご指摘お願いします。

import Mouse
import Generator
import Generator.Standard

sida_width=500
sida_height=500

randomSeed = 12346789
gen = Generator.Standard.generator randomSeed
main = collage (sida_width*2) (sida_height*2) <~ ((f0 13 0 0 gen) <~ (flip (/) sida_width <~ (toFloat <~ Mouse.x)))

-- Original: w1x x y = 0.836 * x + 0.044 * y
w1x x y n = n * x + (1-n) * y
w1y x y = -0.044 * x + 0.836 * y + 0.169
w2x x y = -0.141 * x + 0.302 * y
w2y x y = 0.302 * x + 0.141 * y + 0.127
w3x x y = 0.141 * x - 0.302 * y
w3y x y = 0.302 * x + 0.141 * y + 0.169
w4x x y = 0
w4y x y = 0.175337 * y

f0 k x y gen0 n =
     let (seq, _)=f k x y gen0 n
     in [(move (0,0)) (filled black <| rect (sida_width*2) (sida_height*2))
     ]++seq

f k x y gen0 n =
    if (0 < k) then
      let (rnd1, gen1) = Generator.int32 gen0
          (seq1, gen2) = (f (k - 1) (w1x x y n) (w1y x y) gen1 n)
          (seq2, gen3) = if (rnd1 `mod` 3 == 0) then (f (k - 1) (w2x x y) (w2y x y) gen2 n) else ([], gen2)
          (seq3, gen4) = if (rnd1 `mod` 3 == 1) then (f (k - 1) (w3x x y) (w3y x y) gen3 n) else ([], gen3)
          (seq4, gen5) = if (rnd1 `mod` 3 == 2) then (f (k - 1) (w4x x y) (w4y x y) gen4 n) else ([], gen4)
      in
          (seq1 ++ seq2 ++ seq3 ++ seq4, gen5)
    else
        ((plot (x * sida_width * 0.98)
               (y * sida_height * 0.98)), gen0)

plot x y = [move (x,y) (filled green <| rect 1 1)]

リアクティブプログラミングというのは、私の理解では、時間経過とかマウスクリックとか、なんらかの(外部/外部)イベントに刻々と反応することの記述を容易にできるってことです(雑すぎる説明)。それがファンクショナルなのは、関数型ってことで、elmはさらにピュア・ファンクショナルなので副作用は記述できません。Signalというのが「変化する値」を表わしていて、これはHaskellのIOアクションが副作用を一手に背負っているようなものです。SignalはしかしモナドではなくArrowで、これでFRPを実現しているのを Arrowized FRPと呼ぶとか。ただそこらへんの詳しいことはわからなくても書けます*2

上記のコードは、マウスのx座標で反応するようにしました。FRPなのはmainだけであとは全部ピュアな関数です。x座標に対応して、シダの図のどのパラメータをどう動かすかは、適当にやったので動きは不自然です。あと再帰の深さを深くすると非常に遅くなります。Canvasに書き捨ててるんじゃなくて、描画命令(Form,形状)をリストで持ってるからです。

以下にはコンパイル結果のJSを登録しましたので、ブラウザ上で実際に動かして試すことができます。マウスを左右に動くとなんか動くわけです。

以下は、静止画像のスナップショットです(なので動きません)。

f:id:uehaj:20140703023021p:plain
f:id:uehaj:20140703023027p:plain

枝ぶりが貧弱なのは、速度上の問題ですorz。

以下はelm_dependencies.jsonの内容。標準関数以外には、random generatorというのを使っています。

{
    "version": "0.1",
    "summary": "concise, helpful summary of your project",
    "description": "full description of this project, describe your use case",
    "license": "BSD3",
    "exposed-modules": [],
    "elm-version": "0.12.3",
    "dependencies": {
        "jcollard/generator": "0.3"
    },
    "repository": "https://github.com/USER/PROJECT.git"
}


elmについては別途紹介記事を書くつもり。


「Elmでやってみる」シリーズのまとめエントリ - uehaj's blog

*1:elmをAltJS、すなわちJSの他の選択肢(オルタネティブ)と言うべきなのかはわからない。言うならばJS+CSS+HTML+DOM全体のオルタネティブな気がする。

*2:ちなみにdo記法もproc記法もelmにはありません。lift(n)もしくはアプリカティブスタイルを使って書きます。Haskellの<$>は<~、<*>は~に対応。

Groovyのtraitを使い倒す!

JGGUG G*Workshopに久々に参加しまして、GroovyのTraitについて、以下を話してきました!

盛況でした。いくにんかAndroid開発をされている人もいらっしゃったようで、Groovy on Androidが注目されているのも理由かもしれませんね。

http://togetter.com/li/682540
http://jggug.doorkeeper.jp/events/11365

SpockにおけるMock、Stub、Spyの違い

テスティングフレームワークSpockで相互作用中心のテストをする場合、いわゆるテストダブルとして、MockとStubとSpyの3種類を使えます。それぞれの意味などを簡単に解説します。
Spock全体含め詳しい解説はこちらなどを参照ください。
Mockに関しては、id:yamkazu 氏によるこちらのJGGUG G*Workshopの資料も秀逸です。

Stubとは

最初に、Stub(GroovyStub)についてです。Stubは後述Mockの低機能版であり、具体的には「モッキングができないMock」がStubです。StubができることはMockでできるので、本来Stubを使う必要はありません。モッキングしないことを明示したいなら、使えば良いでしょう。なお、Spock用語の「スタビング」とは別のものです(スタビングおよびモッキングについては後述)。

Mockとは

Mock(もしくはGroovyMock)は、テスト対象が使うクラスを指定して、そのクラスのインスタンスの真似をするオブジェクト(モックオブジェクト)を作ります。真似をするというのは、「元のクラスにあるのと同じシグネチャのメソッドを、モックオブジェクトに対して呼び出したりできる」ということです。Mockのメソッドを呼んでも、Mock元になったオブジェクトの本物のメソッドは呼び出されません。

たとえると、モックは「代役」です。本人は楽屋かなにかで休んでおり、リハーサル(テスト)では代役が呼び出しに応じて努めます。

以下、Mockの使用の例です:

class TestUnit3Spec extends UnitSpec {

   def test00() {
      setup:
        def bean = Mock(SomeBean3)
      when:
        def result = bean.methodOfSomeBean()
        println result
      then:
        1 * bean.methodOfSomeBean() >> 123
        result == 123
   }
}

class SomeBean3 {
   int methodOfSomeBean() {
     println "hoge"
     100
   }
}

上の例では、実際のメソッド methodOfSomeBean では「100」を返しているのにもかかわらず、あるいは"hoge"を出力しているのにもかかわらず、帰ってくる値は「123」だし(assert result=123)、println "hoge"は実行されず、当然"hoge"も出力されません。

ではその代役(モック)は何をするのか?というとですね、モックは、自身のメソッドが呼び出されたときに、「どんなメソッドがどんな引数で呼び出されようとしたか」を記録しており、期待するものだったかどうかを後でチェックできるようになっています。以下がそのチェック部分です.

        1 * bean.methodOfSomeBean() >> 123

この場合、methodOfSomeBeanが1回、呼ばれるならチェックが成功します。


利点としては、上の場合SomeBean3のメソッドが実装されているかどうか、どのように実装されているか、正しいかバグがあるかと関係なく、シグネチャさえ決まっていれば「SomeBean3を使うコード」の試験ができることです。本来「単体試験」というのはかくあるべきだと思いますね。

注意

上の例では、SomeBean3をMockしてますが、「試験対象クラス」がSomeBean3だという想定ではありません。その意味で、上は、試験対象を試験してないテストという変な例です。「試験対象クラスが使用するクラス」に代役を立てることができるのであって、試験対象クラスそのものに代役をたてたら意味ありません。コラボレータとよばれる、「試験対象クラスの動作に必要なクラス」、脇役とか背景の群集とかに、代役を立てるってことです。

Mockまとめ:

  • Mockは代役であり、本人の代りを務める。
    • なので本番オブジェクトのメソッドができあがってなくても、それを使うオブジェクトの試験ができる。
  • Mockに行なわれたメソッド呼び出しの有無と回数、引数などを記録をしてくれる機能ももっている
    • それを後で確認できる。試験がはかどるわ〜。

Spyについて

これに対して、Spy(もしくはGroovySpy)は、その対象とするオブジェクトが、実際に存在する必要があります。偽物だけではなく、本物インスタンスが存在していることが必要です。

Spyの役割りは、「横からメソッド呼び出しの様子を監視し、その呼び出しメソッドや引数を記録するというものです。加えて、最後にその記録された情報、どんなメソッドがどんな回数呼びだされたかをチェックすることができます。Mockと同じようですが、Spyでは戻り値の「本当の値」でのチェックができます。反面、試験対象オブジェクト以外のコラボレータの振舞いに結果が左右され得ることになるので、単体試験としてはどうなんでしょう、という感じもします。

たとえると、スパイは「善意のマンインザミドル攻撃」のようなものです。呼び出し側と対象オブジェクトの間に挟まった代理人で、あとから整合性を確認するための記録を取ってくれます。

なお、Spyでは、スタビング指定すると、実際の値以外の値を返させ、元メソッドを呼ばない動作をさせることもできます。(メソッド個別にMockのように動作させることもできる)。

Spyまとめ:

  • Spyは、本人とのやりとりを横から監視してやりとり記録する
    • なので本番オブジェクトは必要。本番オブジェクトのメソッドの呼び出しの実際の実行、およびその返り値を使っての試験ができる。
      • 本番オブジェクトのメソッドを呼ばずに、特定の別の値を返すこともできる(スタビング指定する)。
      • 本番オブジェクトのメソッドを呼び、かつ、特定の別の値を返すこともできる(スタビングでクロージャ指定, callRealMethod使用)。
  • 自身に行なわれたメソッド呼び出しの有無と回数、引数などを記録をしてくれる機能ももっている
    • それを後で確認できる。試験がはかどるわ〜。

MockとSpyの違いと使い分け

MockとSpyの実用上の最も大きな違いは、Mockの場合はメソッドの返り値がテキトーなもの、0とかnullとかになるということです。実体がないからしょうがありませんね。試験の都合のために、Mockでそれらしい特定の値が返ってほしいときは>>で返り値を明示指定します(スタビング)。

Spyは実際の値がかえるから、>>をする必要はありません。あえて>>しても良いですけれども。

どちらをどのようなときに使い分ければ良いか、という指針ですが、SpockのドキュメントにはSpyについて「(この機能を使用する前に一度考えなおしてください。もしかすると仕様対象のコード設計自体を見なおしたほうが良いかもしれません。)」とのことです。なので、

「なるべくMockで、それでなんらかの不都合があるならSpyで」

でしょうか。

なお、SpockのMock,Spy,Stubという機能は、世に一般的に言う、モック、スパイ、スタブの説明とは微妙な違いがあるかもしれません。上はあくまでSpockのそれらの説明です。

おまけ: モッキングとスタビング

モッキング

Spockにおいて、モッキングとは、テスト対象クラスに対するMock, Spy, Stubのいずれかで、発生を期待するイベントすなわち「どんな引数でのメソッド呼び出しが何回、どんな順序で発生するか」の期待を記述しておくことを言います。

ジョセフ・ジョースターで言えば、「おまえは次に〜する!」「おまえは次に〜言う!」と予告のことです。テストが成功したら、「ハッやってしまったぁ〜!!」と言わねばなりません。

こういう感じになります(以下、こちらより引用)。(*)のところで「1 * ...」しているところがモッキング。


次の例を見てください。

def "should send messages to all subscribers"() {
    when:
    publisher.send("hello")

    then:
    1 * subscriber.receive("hello") // (*)
    1 * subscriber2.receive("hello") // (*)
}

文章としてコードを読んでみると「publisher が ‘hello’ のメッセージを send したとき、 それぞれの subscriber は 1回 message を receive すべき」になります。

スタビング

スタビングは、メソッド呼び出しの返り値を(上書き)指定します。

こちらからまた引用しますが、以下のコード例の (**)のところで>>指定しているのがスタビングです。


固定の値を返す
すでにここまでの例の中で出てきましたが、固定の値を返すには算術右シフト(>>)演算子を使用します。

subscriber.receive(_) >> "ok"

呼び出し毎に異なる値を返すには、それぞれ個別のインタラクションとして定義します。

subscriber.receive("message1") >> "ok" // (**)
subscriber.receive("message2") >> "fail" // (**)

これは"message1"を受信すると"ok"を返し、"message2"を受信すると"fail" を返します。返す値に制限はありませんが、メソッドの戻り値型の制約を外れることはできません。

スタビングとモッキングは同時に行うことも可能です。

1 * subscriber.receive("message1") >> "ok"

モッキングはMockに、スタビングはStubに、という対応はありません。
ただし、Stubはモッキングができず、スタビングのみが行えます。
つまり以下のとおりです。

スタビング モッキング
Mock
Spy *1
Stub ×

*1:固定値を指定してSpyをスタビングすると元メソッドの呼び出しは抑制される。

オブジェクト指向だけじゃない開発宣言

Manifesto for Not Only Object-Oriented Developmentオブジェクト指向だけじゃない開発宣言、というのが出てましたのでごにょと訳してみました*1

オブジェクト指向だけじゃない開発宣言

私たちは、ソフトウェア開発の実践
あるいは実践を手助けをする活動を通じて、
よりよい開発方法を見つけだそうとしている。
この活動を通して、私たちは以下の価値に至った。


クラスよりも、関数と型を、
可変性よりも、純粋性を、
継承よりも、コンポジションを、
メソッドディスパッチよりも、高階関数を、
nullよりも、Optionを、


価値とする。すなわち、左記のことがらに価値があることを認めながらも(但しnullは除くが)、私たちは右記のことがらにより価値をおく。

内容は、「今どきのオブジェクト指向開発宣言」と名乗ってもあんまり違和感ないっす。
ただし、null、おめーは別だ。

追加希望
正格評価よりも、非正格評価を、
チューリングマシンではなく、SKコンビネータを、

追記:この翻訳が本家の日本語訳に採用されました。

*1:元ネタがアジャイル開発宣言のパロなので、訳も借用させてもらいました。

@BaseScriptアノテーションはscriptをインクルードするのに使えます

Groovy 2.2以降、@BaseScriptアノテーションというのが導入され、Scriptの基底クラスを指定することができる*1ようになりました。

これは主目的としては、専用目的の便利機能追加・DSLの作成だと思いますが、単にscriptをインクルードするのにも使えます。

例えば、以下の「OtherScript.groovy」をクラスパスの通ったところか、あるいはカレントディレクトリに配置しておきます。

// OtherScript.groovy
def methodOfOtherScript() {
    "i'm methodOfOtherScript1"
}

def methodOfOtherScript1() {
    "i'm methodOfOtherScript2"
}

println "this is otherScript!"
a=3

これを呼び出す、mainscript.groovyでは以下のように@BaseScriptアノテーションを指定します。

import groovy.transform.BaseScript
@BaseScript OtherScript _

super.run()
println methodOfOtherScript1()
println methodOfOtherScript2()
println a

すると以下のとおり。

$ groovy mainscript.groovy
this is otherScript!
i'm methodOfOtherScript1
i'm methodOfOtherScript2
3

バインディング変数も引き継がれます。

main側のスクリプトを起点には探索してくれないようですが*2、OtherScriptのメソッドが修飾無しでmainscript.groovyから呼び出せます。ちなみに呼びだされるクラスは、大文字で始まらないと認識してくれません。つまり「otherscript.groovy」では駄目でした。

これ、前から欲しいと思ってたんだよね。呼ばれる側をクラス定義+staticメソッドにしてstatic importするとか、でも元のスクリプトスクリプトとして実行できなくなるし、それならスクリプトのままならnewしてインスタンスメソッドとして呼ぶとか、でもバインディング変数の引き継ぎとか駄目だし、mixinとかではあまり上手くいかないし*3

でもこの方法でも、1つのスクリプトで複数のscriptをimportすることは、できませんね。
ここは、ScriptがTraitだったら良かったのにな*4、ってところでしょう。

(追記2014/4/26)
バインディング変数は引き継げませんが、@Delegate+@Fieldでこんなのも可能だと今気づいた。

import groovy.transform.*

@Field @Delegate OtherScript a = new OtherScript()
@Field @Delegate SomeScript b = new SomeScript()

println methodOfOtherScript1()
println methodOfOtherScript2()
println methodOfSomeScript1()
println methodOfSomeScript2()

これなら複数のスクリプトもincludeできますや。
こっちの方がいいかも^^;

*1:従来から、コマンドラインオプションやコンパイラコンフィグレーションでは指定できたが、スクリプト一つで完結する形で指定できるようになった。

*2:動的に明示的にクラスパスに追加すれば可能と思うが

*3:2.2でやはり導入された@DelegatingScriptを@BaseScriptに指定してやれ!と思ったがNPEで不可。バグかなんらかの制限んか。

*4:実際には@BaseScriptが複数親指定を許さないと駄目だから、単にTraitになってもできない。@BaseScript側の対応も必要。

Rustを30分で紹介する(訳)

以下は30 minute introduction to Rustという文書(現在ではオフィシャルドキュメントの一部)の翻訳メモです。C/C++の代替に(将来的に?)なり得る言語として興味を持っています。Heartbleed問題について、ソースが汚いとか、レビュー体制がどうのとか、そもそもSSLの仕様的にどうなの、なんていう議論もあるんでしょうが、人間が間違いを犯す可能性があるということを前提とするなら、問うべき根源的な問題の一つとして、memcpyの使用ミスがあったときに、パスワードのような重要情報を含み得るメモリ領域内全内容を原理的には読まれ得る、っていう根本アーキテクチャを許しておくこと、すなわち、原理主義的にはC言語のごときをセキュアな用途に採用するのが悪い、もしくは採用せざるを得ない状況になっているのが悪いというのが一つとしてあるんじゃねーの、とは思います(誰もが思うでしょうが)。なので、Heartbleedが社会問題化するに伴ない、今後余波で流行る・流行る圧力が増すんではないでしょうか。goやSafeD、などと比べてどんなもんでしょう。



STEVE KLABNIK

最近私は、Rustの包括的なドキュメント化を提案しました。特に、短かくてシンプルな「Rustについて聞いたことがあるけれど、自分にとって適切かどうかを知りたい人向けに紹介する文章」が重要だと思っています。こちらのプレゼンテーションを見て、そのような紹介の基礎となると思いました。この文書を、RFCとしてみてください。コメントはrust-devメーリングリストTwitterにてお願いします。

Rustは、コンパイル時に正しさを保証することに強く主眼を置いた、システムプログラミング言語です。C++やD、Cycloneといった他のシステム言語のアイデアを、強い保証と記憶領域ライフサイクルの明示的制御で改良したものです。Rustでは、強力な記憶領域の保証によって、他の言語よりも容易に正確な並行処理コードを書くことができます。

ここでは、Rustにおける最も重要な概念「オーナーシップ」と、それがプログラマにとっての高難易度タスク「並行処理」の記述にどう影響を及ぼすか、について説明してみます。

オーナーシップ

「オーナーシップ」は、Rustにおいて、主要な、興味深い、もっともユニークな機能の一つです。オーナーシップは、記憶領域の各部分が、プログラムのどの部分によって変更されても良いかを指定します。まずはとあるC++コードを見てみましょう。

int *dangling(void)
{
    int i = 1234;
    return &i;
}

int add_one(void)
{
    int *num = dangling();
    return *num + 1;
}

この関数danglingは整数をスタック上にアロケートして、変数iに格納し、iへの参照を返しますが、一つだけ問題があります。スタック上の記憶領域は関数からリターンすると無効になってしまうということです。これが意味するのは、add_oneの二行目では、numはゴミの値を指しており、望む結果は得られないということです。小さな例ですが、これはしばしば実際にC++のコードで起こります。同様な問題が、malloc(もしくはnew)で割り当てられて解放された後のメモリ領域へのポインタに対して何か処理しようとしたときにも生じます。現代的なC++は、RAIIをコンストラクタ・デストラクタで使用しますが、それでも結局は同じです。この問題は「ダングリングポインタ」と呼ばれるのですが、この問題を持ったRustコードを書くことはできません。やってみましょう。

fn dangling() -> &int {
    let i = 1234;
    return &i;
}

fn add_one() -> int {
    let num = dangling();
    return *num + 1;
}

このプログラムをコンパイルしようとすると、以下のような興味深い(そして長い)エラーメッセージが表示されます。

temp.rs:3:11: 3:13 error: borrowed value does not live long enough
temp.rs:3     return &i;

temp.rs:1:22: 4:1 note: borrowed pointer must be valid for the anonymous lifetime #1 defined on the block at 1:22...
temp.rs:1 fn dangling() -> &int {
temp.rs:2     let i = 1234;
temp.rs:3     return &i;
temp.rs:4 }

temp.rs:1:22: 4:1 note: ...but borrowed value is only valid for the block at 1:22
temp.rs:1 fn dangling() -> &int {      
temp.rs:2     let i = 1234;            
temp.rs:3     return &i;               
temp.rs:4  }                            
error: aborting due to previous error

このエラーメッセージを完全に理解するためには、何かを「所有する(own)」という概念について説明する必要があります。とりあえずここでは、Rustがダングリングポインタのコードを書くことを許さない、ということを知って、それから「オーナーシップ(ownership,所有権)」について理解したあとでこのコードについて再度戻って来て考えることにしましょう。

プログラミングについてはちょっとだけ忘れて、「本」について考えてみましょう。私は物理的な本を読むことが好きですが、時々本当に気に入った本は、友達に「この本は読むべきだ!!」と言うことがあります。私が私の本を読んでいる間、私がこの本を「所有」しています。この本を誰かにしばらく貸したとき、彼は私から本を「借り」ます。もしあなたが本を借りたならば、その本はある期間だけあなたの物になります。そして私に本を返したら、わたしは本を再び「所有」することになります。いいですよね。

このような考え方が、Rustコードではまさに適用されているのです。あるコードはメモリへの特定のポインタを「所有」します。そのコードは、ポインタの唯一の所有者です。メモリをその他のコードにしばらく貸すこともできます。コードは「ライフタイム」と呼ぶある期間だけ、そのメモリを「借りる」のです。

これだけです。難しくないですよね。さて先のエラーメッセージに戻ってみましょう。エラーメッセージはこう言っています「借りた値は十分長くは生存しない(borrowed value does not live long enough)」。ここではRustの借用ポインタ(Borrowed Pointer)「&」を使ってiという変数を貸しだそうとしています。しかしRustは変数は関数からリターンすると無効になることを知っているので、こう表示します。「借用ポインタは無名ライフタイム#1の期間有効でなければならないが、借用値はこのブロックでのみ有効である」。すばらしい!

これはスタックメモリの良い例ですが、ヒープについてはどうでしょう。Rustは二番目の種類のポインタ「ユニーク」ポインタを持っています。(訳注: Rustの仕様がかわり、~は最新のRustではシンタックスとしては削除された。その代わりに、型としてはBox<T>トレイト、Boxを作成するにはBox::new()を使用する*1。この機能はOwned BoxもしくはOwnning Pointer、もしくは単にBoxなどと呼ぶことが多いようだ。)

fn dangling() -> ~int {
    let i = ~1234;
    return i;
}

fn add_one() -> int {
    let num = dangling();
    return *num + 1;
}

このコードは成功裏にコンパイルできます。スタックに割り当てられる1234の代りに、その値への所有ポインタを代りに使っていることに注意してください。大ざっぱには以下のように対応します。

// rust
let i = ~1234;
// C++
int *i = new int;
*i = 1234;

Rustはそのデータ型の格納のために必要な正しい量のメモリを割り当て、指定した値が設定されます。これが意味するのは、初期化されていないメモリが割り当てられることはないということです。Rustはnullの概念を持っていません。やったー!!! RustとC++では一つ違いがあります。Rustコンパイラはiのライフタイムを知っているので、それが無効になったときに対応するfree呼び出しをC++のデストラクタのように挿入してくれるのです。保守を自分でやらなくても、手動でヒープメモリを割り当てる利点を全て得ることができるのです。さらに、ランタイムのオーバーヘッドはありません。あなたはC++で正しく書いたのと(基本的には)正確に同じコードを得ることができ、間違ったバージョンを書くことはできません。ありがとうコンパイラ!

所有権とライフタイムというものが、厳しくない言語で書いた場合に通常危険になってしまうコードを避けるために有用である例を1つを見てきました。さて次に、もう1つの話題を示しましょう。並行性です。

並行処理

並行処理は、ソフトウェアの世界では今やびっくりするほど熱い話題です。計算機科学の研究では、従来から常に興味深い領域でしたが、インターネットの爆発的な利用拡大に伴なって、サービスあたりの利用者数をさらに増やすことが求められています。でも並行処理コードにはかなり大きな不利益があります。非決定性です。並行処理コードを書くための異なるアプローチはいくつかあるわけですが、ここでは所有権とライフタイムの概念を使ったRustの記法が、正しい並行処理を書くのをどう助けてくれるかを見てみましょう。

最初に、単純な並行処理を実行するRustのサンプルコードを示します。Rustは「タスク」を起動(spin up)することを許します。タスクは軽量なグリーンスレッドです。これらのタスクは共有メモリをもたず、チャネルを通じて以下のように通信します。

fn main() {
    let numbers = [1,2,3];

    let (port, chan)  = Chan::new();
    chan.send(numbers);

    do spawn {
        let numbers = port.recv();
        println!("{:d}", numbers[0]);
    }
}

この例では、数値のvectorを作成します。われわれはまず、Chanをnewします。ChanというのはRustがチャンネルを実装するパッケージ名です。これは2つの異なるチャンネル端である「channel」と「port」を返します。channel端にデータを送信し、port端からデータを取り出します。spawn関数は新しいタスクを起動(spin up)します。上のコードで判るように、新しいタスクの内側でport.recv()を呼んでおきます(recvは'receive'(受信)の短縮)。新しいタスクの外側からvectorを渡してchan.send()を呼びます。そして内側のタスクでは受け取ったvectorの最初の要素を表示します。

これはRustがvectorをチャンネルを経由して送信する際にコピーするので動作します。このように、もしvectorがミュータブルであったとしても、競合条件にはなりません。しかしながら、大量のタスクを作ったんら、あるいはデータが巨大な場合、タスクごとにコピーしてしまうとメモリを無駄に使ってしまいます。そうすることの利点もありません。

Arcを見てみましょう。Arcは「atomically reference counted(自動的な参照カウント)」の頭字語であり、不変データを複数のタスク間で共有する方法です。以下、コード例です。

extern mod extra;
use extra::arc::Arc;

fn main() {
    let numbers = [1,2,3];

    let numbers_arc = Arc::new(numbers);

    for num in range(0, 3) {
        let (port, chan)  = Chan::new();
        chan.send(numbers_arc.clone());

        do spawn {
            let local_arc = port.recv();
            let task_numbers = local_arc.get();
            println!("{:d}", task_numbers[num]);
        }
    }
}

このコードは先ほどのコードととても似ていますが、3回ループしているところが違います。3つのタスクを作成し、それらにArcを送ります。Arc::newは新しいArcを作成し、.clone()は新しいそのArcへのリファレンスを作ります。.get()はArcから値を取り出します。まとめると、それぞれのタスク用に作成した新しいリファレンスをチャンネルに送信し、リファレンスを使って数字を表示します。この場合vectorはコピーされません。

Arcは変更不可データを扱うのには素晴しいですが、変更可能データについてはどうでしょう? 可変状態の共有は、並行処理プログラマにとって悩みのたねです。共有される可変状態を守るのにmutexが使えますが、mutexを取得するのを忘れたら不幸が起き得るでしょう。

Rustは可変状態を共有するためのツールであるRWArcを提供します。こちらは内容変更を許すArcの変種です。以下を確認してみてください。

extern mod extra;
use extra::arc::RWArc;

fn main() {
    let numbers = [1,2,3];

    let numbers_arc = RWArc::new(numbers);

    for num in range(0, 3) {
        let (port, chan)  = Chan::new();
        chan.send(numbers_arc.clone());

        do spawn {
            let local_arc = port.recv();

            local_arc.write(|nums| {
                nums[num] += 1
            });

            local_arc.read(|nums| {
                println!("{:d}", nums[num]);
            })
        }
    }
}

この場合RWArcパッケージで「読み書き可能Arc」を取得します。「読み書き可能Arc」のAPIはArcとは少しだけ異なり、readとwriteがそれぞれデータを読み書きします。これらの関数はクロージャを引数として取ります。Arcに対するwriteはmutexを獲得し、クロージャにデータを渡します。クロージャーの実行後、mutexを解放します。

こうなっているので、mutexを獲得するのを忘れて状態を変更することはできません。可変状態の共有禁止と同じ安全性を保ちながら、可変状態を共有による効率性も得ることができます。

でも、ちょっと待ってくださいよ。そんなことが可能なんでしたっけ? 可変状態を許しつつ禁止することなんかできないはずです。いったいどうなってるんだ?

unsafeについての脚注

Rust言語は共有可変状態を許しませんが、それをするコード例を示しました。どうしてこんなことが可能なんでしょうか? 答えはunsafeです。

Rustコンパイラはとても賢く、失敗しがちな場面で守ってはくれるのですが、人工知能ではありません。私たちはコンパイラより賢いので(時々ですが)、この安全を優先する動作を上書きする必要があるのです。この目的のために、Rustにはunsafeキーワードがあります。unsafeブロック中ではRustは安全性チェックをオフにします。異常があったときは、注意深くコードを監査する必要があるのはunsafeの内側のみであり、プログラム全体ではありません。

もし主要な目的の一つが安全性であるなら、その安全性をオフにできるのはなぜでしょうか? 主には3つの理由があります。(1)例えばFFIを使ったCライブラリなどの外部コードとのインターフェース、(2)パフォーマンスのため(不可避なケースがある)、(3)通常は安全ではない操作の上に安全な抽象を提供するケース、です。Arcsは最後の目的達成のための例になっています。複数の参照をArcで分配することができますが、そのデータを変更不可であると確かに知ってるからで、共有しても安全です。複数のRWArcのリファレンスを分配することもできますが、なぜならそのデータがmutexでラップされることを知っているからであり、共有しても安全です。しかし、Rustコンパイラはこのような選択をしたことを知ることはできません。Arcの内部実装では、(通常は)危険なことをするためにunsafeブロックを使用しています。しかし、我々が公開するのは安全なインターフェースなので、Arcsの使用者は誤まった使用をすることができません。

並列処理プログラミングを困難にするようなミスを発生不可能にし、さらにC++のような言語の効率性も得るために、Rustの型システムがやっているのはこういうことです。

どうもありがとう、みんな

このRustについてのちょっとした情報で「おお、Rustはおれにとって良い言語だわ」と思ってくれたら有り難いです。もしそうなら、Rustの文法と概念についての説明についてのチュートリアルをチェックしてみて下さい。


訳は以上。以下、他の情報

*1:box という記法もあるが現時点ではunstable featureである

Groovyを勉強したい人に贈る、astprintコマンド

今ごろ言うな、って話かもしれませんが、自分で2年ほど前にこっそり作っていて最高に便利だと思っているコマンド「astprint」を紹介します。

astprintの内容は以下のようにシェルスクリプトです。

#!/bin/sh

groovy -e 'groovy.inspect.swingui.AstNodeToScriptAdapter.main(args)' $*

やってることは"こちら"で紹介したAstNodeToScriptAdapterを呼んでるだけです。以下ようにコンパイルの途中経過が標準出力にテキストで出力されるので簡単に見れます。本当便利です。

% cat hoge.groovy
cat hoge.groovy
trait FileNameIsHogeHoge {
    @groovy.transform.ForceOverride
    String getPath() {
        return "hogehoge"
    }
}

class MyFile extends File implements FileNameIsHogeHoge {
    MyFile(String fileName) {
        super(fileName)
    }
}

f = new MyFile("/tmp/file.txt")
assert f.getPath() == "hogehoge"

% astprint hoge.groovy 4
astprint hoge.groovy 4
f = new MyFile('/tmp/file.txt')
assert f.getPath() == 'hogehoge' : null
public class script1397699411969 extends groovy.lang.Script { 

    public script1397699411969() {
    }

    public script1397699411969(groovy.lang.Binding context) {
        super.setBinding(context)
    }

    public static void main(java.lang.String[] args) {
        org.codehaus.groovy.runtime.InvokerHelper.runScript(script1397699411969, args)
    }

    public java.lang.Object run() {
        f = new MyFile('/tmp/file.txt')
        assert f.getPath() == 'hogehoge' : null
    }

}
@groovy.transform.Trait
abstract interface public class FileNameIsHogeHoge extends java.lang.Object { 

    @groovy.transform.ForceOverride
    @org.codehaus.groovy.transform.trait.Traits$Implemented
    abstract public java.lang.String getPath() {
    }

}
public class MyFile implements FileNameIsHogeHoge extends java.io.File { 

    public MyFile(java.lang.String fileName) {
        super(fileName)
    }

}
abstract public static class FileNameIsHogeHoge$Trait$Helper extends java.lang.Object { 

    public static void $init$(FileNameIsHogeHoge $self) {
    }

    public static void $static$init$(java.lang.Class<FileNameIsHogeHoge> $static$self) {
    }

    @groovy.transform.ForceOverride
    public static java.lang.String getPath(FileNameIsHogeHoge $self) {
        return 'hogehoge'
    }

}

これで見るとtraitは@groovy.transform.Trait指定になってるんですねー。4はコンパイルのフェイズで、以下の意味があります。

数字 シンボル 説明
1 INITIALIZATION ファイルを開いたり
2 PARSING 字句・構文解析ANTLRのAST構築
3 CONVERSION CSTからASTへの変換
4 SEMANTIC_ANALYSIS ASTの意味解析と解明
5 CANONICALIZATION ASTの補完
6 INSTRUCTION_SELECTION クラス生成(フェーズ1)
7 CLASS_GENERATION クラス生成(フェーズ2)
8 OUTPUT クラスをファイルに出力
9 FINALIZATION 後始末
9 ALL 後始末まですべて実行

groovyConsoleでも同じ内容が表示できますが、コマンドラインからできるのも大変便利です。
traitについては7ぐらいにするともっと展開された結果になりますが、バイトコード生成レベルではなく、かなりソースコードに近い側で展開処理してるようで参考になます。