Grails 3.0先取り!? Spring Boot入門ハンズオン
次回G*Workshopは、8/1 「Grails 3.0先取り!? Spring Boot入門ハンズオン」です。
ふるってのご参加をお待ちしております。
Grails 3.0先取り!? Spring Boot入門ハンズオン
申し込みはこちらから>
Grails 3.0先取り!? Spring Boot入門ハンズオン
予定の内容
Grailsの次期バージョン3.0でベースになることが予定されている、Spring界隈の新しいトレンド"Spring Boot"のハンズオンを通じて、Spring Bootのイメージを掴んでもらいたいと思います。内容は以下の通りです。
Spring Boot概要説明
Spring Bootを用いて簡単なアプリケーションを実際に作ってみる
(合計で約二時間弱)
「プログラムでシダを描画する」を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を登録しましたので、ブラウザ上で実際に動かして試すことができます。マウスを左右に動くとなんか動くわけです。
以下は、静止画像のスナップショットです(なので動きません)。
枝ぶりが貧弱なのは、速度上の問題です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については別途紹介記事を書くつもり。
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は除くが)、私たちは右記のことがらにより価値をおく。
内容は、「今どきのオブジェクト指向開発宣言」と名乗ってもあんまり違和感ないっす。
ただし、null、おめーは別だ。
追加希望
正格評価よりも、非正格評価を、
チューリングマシンではなく、SKコンビネータを、
追記:この翻訳が本家の日本語訳に採用されました。
@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できますや。
こっちの方がいいかも^^;
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の文法と概念についての説明についてのチュートリアルをチェックしてみて下さい。
訳は以上。以下、他の情報
- Rustで書かれた実験的ブラウザエンジンServoのソースの一つ。#[deriving(Eq, Clone)]って型クラス採用してるからhaskellぽい
- Rust - Mozilla の開発したシステムプログラミング言語 - に関するインタビュー
- スレッドモデルを変更したRust 0.9がリリース「バージニア大学の学部向けOSコースではRustが教えられている」
- The Rust Language Tutorial0.10
- Rust言語チュートリアル日本語訳。やや古いとのこと。
- 5分で分かったふりができるRust紹介
- Rust for Rubyists
- Rustのタスクについて
*1:box という記法もあるが現時点ではunstable featureである