無限大な夢のあと

テニスとアニメが大好きな厨二病SEのブログ

Rust入門 #0 Macで開発環境構築 (2017/1/4時点)

※1/9に以下の対応を追加。

  1. メソッドジャンプをする対応方法をrustyを使う方法に変更。
  2. cargo-editをインストールする方法を追記。

皆様、あけましておめでとうございます。
2017年、よろしくお願い致します。

今年の一発目のブログはRustの開発環境構築です。
Rust自体は前職の方からも名前だけは聞いていたのですが、以下のちょっとしたきっかけで入門してみようと思いました。

  1. 社内のIoTチャレンジ部で、ラズベリーパイで遊んでいた時に、省メモリでかつ関数型っぽく記述できると話題に上がったこと。
  2. 書評レビューア募集で、Android開発の書籍を頂いたけど、 そのサンプルを新しい言語で動かしてみたいなと思ってみたこと。
  3. 王様達のバイキングの11巻で、ValkyrjaというクラッカーがScalaやRustでプログラム書いていたという話が出てきたこと。(←これが一番の理由w)

王様達のヴァイキング 11 (ビッグコミックス)

王様達のヴァイキング 11 (ビッグコミックス)

matome.naver.jp

また、2017/1/2時点でIntelliJのRustプラグインを用いても、メソッドのコード補完などもできないため、コード補完プラグインが存在するatomを用いることとします。

Macのスペック

  • マシン: MacBookPro (Retina,15-inch, Mid 2015)
  • OS: OS X EI Capitan 10.11.6
  • プロセッサ: 2.2 GHz Intel Core i7
  • メモリ: 16GB 1600 MHz DDR3

atom

  • バージョン 1.12.7
  • 日本語化対応済み


  1. Rustをインストール。選択肢では、1を選択。

    curl https://sh.rustup.rs -sSf | sh
    This path will then be added to your PATH environment variable by modifying the
    profile file located at:
    
      /Users/ユーザ名/.profile
    
    You can uninstall at any time with rustup self uninstall and these changes will
    be reverted.
    
    Current installation options:
    
       default host triple: x86_64-apple-darwin
         default toolchain: stable
      modify PATH variable: yes
    
    1) Proceed with installation (default)
    2) Customize installation
    3) Cancel installation
    


  2. 今回の手順では、.bashrcにPATHの記載を追加していく。
    ただし、Mac OS Xのデフォルトでは、HOMEに.bashrcを作成してもターミナル起動時に自動で読み込むようにはなっていないようなので、自動で読み込むように.bash_profileに以下の記述を追加。

    atom ~/.bash_profile
    if [ -f ~/.bashrc ] ; then
    . ~/.bashrc
    fi

  3. RustのパッケージマネージャーであるCargoのパスを通すために、~/.bashrcファイルに設定を追加。

    atom ~/.bashrc
    #Rust 
    export PATH="$HOME/.cargo/bin:$PATH"


  4. bashrcの設定を反映する。

    source ~/.bashrc


  5. rustc、cargo、rustupがインストールされたことを確認。

    rustc --version
    rustc 1.14.0 (e8a012324 2016-12-16)
    
    cargo --version
    cargo 0.15.0-nightly (298a012 2016-12-20)
    
    rustup --version
    rustup 1.0.0 (17b6d21 2016-12-15)


  6. 補完に必要なパッケージをインストールする。

    cargo install racer


  7. cargo .tomlを操作するのに便利なパッケージをインストールする。

    cargo install cargo-edit


  8. atomにRust用のプラグインとして、下記をインストールする。
    atom自体のインストールは省略。

    apm install language-rust
    apm install racer
    apm install linter
    apm install linter-rust
    apm install atom-ctags
    apm install script


  9. racerというRustのコード補完を行うプラグインで必要なため、Rustのソースコードを落とす。

    rustup component add rust-src


  10. atomを起動し、環境設定>Packagesで、racerの設定を開く。

  11. 下記の設定項目を入力する。※フルパスでないと動作しなかった。

    Path to the Racer executable

    /Users/ユーザ名/.cargo/bin/racer

    Path to the Rust source code directory

    /Users/ユーザ名/.rustup/toolchains/stable-x86_64-apple-darwin/lib/rustlib/src/rust/src


  12. メソッドジャンプを可能にするためにrusty-tagsを使用する。
    1. ctagsをインストール。

      brew tap universal-ctags/universal-ctags
      brew install --HEAD universal-ctags


    2. rusty-tagsをインストール。

      cargo install rusty-tags



  13. パッケージ管理を容易にできるcargo-editをインストール。

    cargo install cargo-edit


  14. 動作確認として、プロジェクト作成する。

    cargo new hello_world --bin


  15. Rustのコードを実行。
    1. コマンドライン

      cd hello_world/
      cargo run #Cargo.tomlのあるディレクトリで実行


    2. atom
      atomでファイルを開いている状態で、scriptプラグインのショートカット(Command+i)で実行する。


    これでRustの開発環境構築ができて、書き始められるはず!


    今回、参考にさせて頂いた公式チュートリアル/ブログ。

    はじめる

    • atomの導入について参考にさせて頂きました。

    qiita.com

    Scalaスケーラブルプログラミング第3版

    Scalaスケーラブルプログラミング第3版

    Programming Rust: Fast, Safe Systems Development

    Programming Rust: Fast, Safe Systems Development

    Atom実践入門──進化し続けるハッカブルなエディタ (WEB+DB PRESS plus)

    Atom実践入門──進化し続けるハッカブルなエディタ (WEB+DB PRESS plus)

    テキストエディタAtom入門 (OIAX BOOKS)

    テキストエディタAtom入門 (OIAX BOOKS)

【書評】改訂新版JavaScript本格入門 ~モダンスタイルによる基礎から現場での応用まで( @yyamada ) 

今回もWINGSプロジェクトの書籍レビュアーに応募し、献本してもらったので、書評を書きました。
WINGSプロジェクトの皆様、著者の山田様ありがとうございます。
今回は表題の書籍を献本していただきましたので、こちらのレビューをさせていただきます。

こちらのJavaScriptの書籍ですが、改訂前の書籍はJavaScriptの一番最初の書籍でした。
ただ、私のJavaScriptの経験は以下で、初心者レベルです。
・研修としてJavaScriptで課題をこなす(文法基礎、コンストラクタとクロージャー、クライアントJavaScriptクロージャー etc)
・実案件ではデモ画面に修正を加える。
・自分のアプリを作成するにあたり、Angular2+Bootstrapで軽く画面を作成。
・転職後に、DDD研修の際にクライアントサイドをAngular.jsで作成。

ただ、転職後にクライアントサイドを触る機会が増えるということで、今回改めてこちらの書籍でしっかりと勉強し直すというところです。
そして、このタイミングで、来月から管理画面のAngularJSに新規画面の追加チケットがあるので、絶好のタイミングです。

ここから書籍の具体的な内容の紹介として、セールスポイントを大きく3つさせていただきます。


①ES2015の記法を追加された箇所にマークが付いていて、分かりやすい。

ざっと読んで、以下の部分がES2015で追加された箇所となります。
・let
・const
・テンプレート文字列
・分割代入(配列/オブジェクト)
・for of 命令
・シンボルオブジェクト
・StringやNumber、Objectオブジェクトなどに追加されたメソッド
・Mapオブジェクト
・Setオブジェクト
RegExpオブジェクトのuフラグ(Unicodeサロゲートペアを認識できる)
・アロー関数
・引数のデフォルト値
・可変長引数
・...演算子
・名前付き引数
オブジェクト指向構文(class、オブジェクトリテラル、モジュール、イテレータ、ジェネレータ、Proxyオブジェクト)
・Promiseオブジェクト
etc

TypeScriptが先取りしたこともあり、これもJavaScriptでできるようになったんだという印象でした。


②GruntやBabelなど、トレンドを押さえている。

GulpやWebPackなどフロントエンドの進化はすさまじいですが、最初の走りだった(と認識している)Gruntを押さえている。
実際に現場に入る際には、現場で使っているツールを学ぶ前にそれがそもそもなぜ必要なのか、なぜ自分の現場ではその上で違うツールを使っているのかを考える際の材料/ヒントが書いているなぁという認識でした。
私は今まさにこれを読んで勉強しているところです。
ちなみにうちの現場ではプロダクトによって、Grunt+TypeScript or ES2015(Babel)となっています。



③図がふんだんに使われていて、説明がわかりやすい

これは改訂前からですが、初めて読んだ時は以下の説明などが図やスクリーンショットが多くわかりやすかったです。
ブラウザー付属の開発者ツール
・クエリ情報をエスケープする
・スコープチェーン
クラスタイプとプロトタイプのオブジェクト指向の違い
・mouseover/mouseoutとmouseenter/mouseleaveの違い
・フォーム全般
・イベントの伝搬(キャプチャ/ターゲット/パブリング)
Ajax全般

Ajaxとか初めての実装の時に最初にわかるまで苦労したなぁ(遠い目)



JQueryのコピペからの卒業にはぴったりの1冊なので、ぜひ皆さん手にとって読んでみてください。

ちなみに私がJavaScriptを学ぶ上で他に参考にした書籍を紹介いたします。ご参考までに。

文法よりの書籍だけど、全てがオブジェクトだということが分かる書籍。

開眼!  JavaScript ―言語仕様から学ぶJavaScriptの本質

開眼! JavaScript ―言語仕様から学ぶJavaScriptの本質

JavaScript本格入門とこいつを照らし合わせて、理解を深める。もはや辞書。

パーフェクトJavaScript (PERFECT SERIES 4)

パーフェクトJavaScript (PERFECT SERIES 4)

リファレンスとして使える。ES2015版のものも出ないかなー

改訂第5版 JavaScript ポケットリファレンス

改訂第5版 JavaScript ポケットリファレンス

DDDで掲示板を作るという研修で、仕事で初めて本格的にAngularJSでアプリを作ることになった際にかなり参考になった書籍。

AngularJS アプリケーションプログラミング

AngularJS アプリケーションプログラミング


以上、簡単ではございますが書評のレビューとさせていただきます。

「エリック・エヴァンズのドメイン駆動設計」を読んで(自分用メモ) 第2部 モデル駆動設計の構成要素(前半 4章-5章)

DDD導入研修の課題として、下記の書籍「エリック・エヴァンズのドメイン駆動設計」を読むことになりました。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

本文章は上記の書籍を引用させて頂きます。


ただ漠然と読んでいるだけだと、頭を通りすぎていきそうなので、自分なりに要点や響いた箇所、メモを残しながら読んでいこうと思います。
「Domain-Drive Design Quickly」は約80ページしかありませんでしたが、こちらはボリュームが大きいので、覚悟しながら読もうと思います。

Kindle書籍からだとコピーできないため、簡単にまとめていきます。

☆第2部 モデル駆動設計の構成要素

・introduction

f:id:noimpslmtbrk:20160915132551p:plain

モデル駆動設計を構成する言語のナビゲーションマップとのことです。
まだ、中身の用語の定義が曖昧なままなので、ここは読んでいく上で何度も立ち戻りたい。


・第4章 ドメインを隔離する。

レイヤ化アーキテクチャ
f:id:noimpslmtbrk:20160915152220p:plain

・ユーザインタフェース(プレゼンテーション層)
 ユーザに情報を表示して、ユーザのコマンドを解釈する責務を負う。外部アクタは人間のユーザではなく、別のコンピュータシステムのこともある。


・アプリケーション層
 ソフト雨ケアが行うことになっている仕事を定義し、表現力豊かなドメインオブジェクトが問題を解決するように導く。このレイヤが責務を負う作業は、ビジネスにとって意味があるものか、あるいは他システムのアプリケーション層と相互作用するのに必要なものである。
 このレイヤは薄く保たれる。
 ビジネスルールや知識を含まず、やるべき作業を調整するだけで、実際の処理は、ドメインオブジェクトによって直下のレイヤで実行される共同作業に移譲する。ビジネスの状況を反映する状態は持たないが、ユーザやプログラムが行う作業の進捗を反映する状態を持つことはできる。


ドメイン
 ビジネスの概念と、ビジネスが置かれた状況に関する情報、およびビジネスルールを表す責務を負う。ビジネスの状況を反映する状態はここで制御され、使用されるが、それを格納するという技術的な詳細は、インフラストラクチャに移譲される。この層がビジネスソフトウェアの核心である。


・インフラストラクチャ層
 上位のレイヤを支える一般的な技術的機能を提供する。これには、アプリケーションのためのメッセージ送信、ドメインのための永続化、ユーザインターフェースのためのウィジェット層などがある。インフラストラクチャ層は、ここで示す4層間における相互作用のパターンも、アーキテクチャフレームワークを通じてサポートすることもある。


プロジェクトによっては、ユーザインタフェースそうとアプリケーション層を厳密に区別しないこともある。また、複数のインフラストラクチャ層を持つこともある。しかし、ドメイン層を分離して初めて、モデル駆動開発が可能になるのだ。

ここは各レイヤの説明と共に残しておく。

複雑なプログラムはレイヤに分割すること。各レイヤで設計を進め、凝集度を高めて下位層にだけに依存するようにすること。
標準的なアーキテクチャーパターンに従って、上位のレイヤに対しては疎結合にすること。
ドメインモデルに関係するコード全部を1つの層に集中させ、ユーザインタフェース、アプリケーション、インフラストラクチャのコードから分離すること。
表示や格納、アプリケーションタスク管理などの責務から解放されることで、ドメインオブジェクトはドメインモデルを表現するという責務に専念できる。これによって、モデルは十分豊かで明確になるように進化し、本質的なビジネスの知識を捉えて、それを機能させることができるようにする。

ここも実際に適用する際のメモとして、残しておく。

レイヤを関係づける

レイヤ同士は疎結合であるべきで、設計の依存関係は1方向にだけ向けられる。
上位のレイヤは下位のレイヤにある要素を直接使用した入り、操作したりできる。

下位のレイヤにあるオブジェクトが上方と通信が必要な場合は、コールバックやオブザーバといったようなレイヤ同士を関係づけるためのアーキテクチャーパターンである。

ここも原理原則ではあるが、メモ。

利口なUI「アンチパターン

アプリケーション処理をドメイン層から分離したくないパターン

■利点
・ 単純なアプリケーションの場合、生産性が高く、すぐに作れる。
・ それほど有能でない開発者でも、この方法ならほとんど訓練しないで仕事ができる。
・ 要求分析が不足していても、プロトタイプをユーザに公開し、その要望を満たすように製品を変更することで、問題を克服できる。
・ アプリケーションが互いに分離しているので、小さなモジュールの納品スケジュールは比較的正確に計画できる。単純な振る舞いを付け加えるようなシステムの拡張であれば、容易に対応できるだろう、
・ 関係データベースはうまく機能し、データレベルでの統合が実現される。
・ 4GL ツールが実にうまく機能する。
・ アプリケーションが引き継がれた場合、保守プログラムは自分が理解できない部分を素早く作り変えられる。変更による影響が、それぞれ特定のユーザインタフェースに限定されるからだ。

■欠点
・ アプリケーションの統合は困難で、データベースを経由させるしかない。
・ 振る舞いが再利用されることも、ビジネスの問題が抽象化されることもない。ビジネスルールは、適用先の操作それぞれで複製されることになる。
・ 迅速なプロトタイピングやイテレーションを行おうとしても、自然と限界に行き当たる。抽象化が欠けているために、リファクタリングの選択肢が制限されるからだ。
・ 複雑さによって、すぐに覆い尽くされてしまうので、成長しようとしても、単純なアプリケーションを追加することしかできない。より豊かな振る舞いが実現できるようになるといった、優雅な道は存在しない

Viewにロジックが混ざったみたいな状況のことを指しているということみたい。
ドメイン駆動設計とは反対の思想で大規模で複雑なものには採用するべきではない。


・第5章 ソフトウェアで表現されたモデル 

あるオブジェクトは、状態が異なったり、さらに別々の実装をまたいだ入りしたとしても追跡されるような、連続性と一意性を持ったものを表現しているのか?
それとも、他の何かの状態を記述する属性なのか?
これがエンティティと値オブジェクトとの基本的な区別である。
なんらかのパターンに従うオブジェクトを定義することで、そのオブジェクトは曖昧ではなくなり、強固な設計を行うための具体的な選択に向かう道筋が整えられる。

エンティティと値オブジェクトの話は、後でも説明は出ると思うけど、ここは押さえておく。

次に、ドメインの側面によっては、オブジェクトとしてよりも、アクションや操作として表現した方が明確になるものもある。
オブジェクト指向モデリングの伝統からやや外れるが、これらについてはサービスで表現して、操作を行う責務をエンティティや値オブジェクトに押し付けない方が適切であることも多い
サービスとは、要求に応じてクライアントのために行われる何かである。ソフトウェアの技術的なレイヤーには多くのサービスがある。また、サービスはドメインにも登場する。その際にモデル化されるのは、ソフトウェアが実行すべきことに対応し、状態には対応しないような活動だ。

まだここはピンとこない。

最後に、モジュールに議論することによって、設計上のあらゆる意志決定は、ドメインについてのなんらかの洞察によって動機付けられないければならないという点を強調する。
高凝集と低結合という考え方は、しばしば技術的な指標と考えられているが、概念そのものにも適用できる。モデル駆動設計においては、モジュールはモデルの一部であり、ドメインにおける概念を反映していなければならない。

ここは一旦メモ。

■関連
関連をもっと扱いやすくするには、少なくとも3つの方法がある。
1.関連をたどる方向を強制する。
2.限定子を付加して、多重度を効果的に減らす。
3.本質的ではない関連を除去する。

ここも一旦メモ。

■エンティティ(参照オブジェクト)

オブジェクトモデリングを行うと、我々はオブジェクトの属性に集中しがちだが、エンティティの根本的な概念は抽象的な連続性である。
この連続性はエンティティのライフサイクルを通じて続き、エンティティが多様な形を取っても変わることがない。

オブジェクトの中には、主要な定義が属性によってなされないものもある。そういうオブジェクトは同一性のつながりを表現するのであり、その同一性は、時間が経っても、異なる形で表現されても変わらない。そういうオブジェクトは属性が異なっていても、他のオブジェクトと一致しなければならないことがある。
また、あるオブジェクトは、同じ属性を持っていたとしても、他のオブジェクトと区別しなければならない。同一性を取り違えるとデータの破損につながりかねない。

同一性が一つのキーワードかな。

他方、モデル中の全てのオブジェクトが、意味のある同一性を持ったエンティティであるとは限らない。この問題を複雑にしているのは、オブジェクト指向言語が、あらゆるオブジェクトに「同一性」の演算を組み込んでいるという事実である。(Javaの=演算子など)
これはメモリ中の位置の比較やその他の仕組みによって実現される。その意味で、全てのオブジェクトインスタンスは同一性を持っている。
例えば、Javaの実行環境を作成するドメインや、リモートオブジェクトをローカルでキャッシュする技術的なフレームワークドメインであれば全てのオブジェクトインスタンスがまさにエンティティになるだろう。
しかし、この同一性の仕組みはほかのアプリケーションドメインでは意味をなさない。
同一性は、エンティティの持つ巧妙な意味のある属性であり、プログラミング言語の持つ自動化された機能は引き継げないのである。

最後の1文だけ頭に残しておく。

あるオブジェクトが属性ではなく、同一性によって識別されるのであれば、モデルでこのオブジェクトを定義する際には、その同一性を第一とすること。クラスの定義をシンプルに保ち、ライフサイクルの連続性と同一性に集中すること。クラスの定義をシンプルに保ち、ライフサイクルの連続性と同一性に集中すること。形式や履歴に関係なく、各オブジェクトを識別する手段を定義すること。オブジェクト同士を突き合わせる際に、属性を用いるよう求めてくる要件には注意すること。各オブジェクトに対して結果が一意となることが保証される操作を定義すること。これは一意であることが保証された記号を添えることで、おそらく実現できる。この識別手段は外部に由来する場合もあれば、システムによってシステムのために作成される任意の識別子の場合もあるが、モデルにおける同値性の区別とは一致しなければならない。モデルは同じものであるとうことが何を意味するかを定義しなければならない。

ライフサイクルの連続性というのが頭に?が浮かんでいる。

・エンティティをモデル化する。
オブジェクトをモデル化する時に、属性について考えるのは自然なことであり、その振る舞いについて考えることにも極めて重要だ。
しかし、エンティティにとって最も基本的な責務は、振る舞いが明確で予測可能になるよう、連続性を確立することである。
これが一番うまくいくのは、余計なものがない状態が保たれている時だ。
属性や振る舞いに集中するよりは、エンティティオブジェクトの定義を最も本質的な特徴にまで削ぎ落とすこと。

連続性を確立することというのが、いまいちピンとこない。
また、同一性のための操作を設計するというのは現実の問題と照らしわせると難しい。


■値オブジェクト(VALUE OBJECTS)

多くのオブジェクトには概念的な同一性がない。そういうオブジェクトは、物事の特徴を記述する。

モデルで最も目立つオブジェクトが、通常はエンティティであり、また各エンティティの同一性を追跡することが非常に重要であることから、あらゆるドメインオブジェクトに同一性を割り当てようと考えるのは自然なことである。実際、フレームワークの中には、あらゆるオブジェクトに一意のIDを割り当てるものもある。

エンティティの同一性を追跡するのは本質的なことだが、それ以外のオブジェクトに同一性を与えてしまうと、システムの性能を損なうことにアンリ、分析作業が増え、さらに、全てのオブジェクトの見た目が同じになってしまうことでモデルが台無しになりかねない。
ソフトウェア設計は、複雑さとの恒常的な戦いである。
特別な処理が必要な場所だけで行われるように、区別しなければならない。
しかし、このオブジェクトのカテゴリを、単に同一性のないものとみなしてしまうと、我々の使えるツールや語彙は大して増えない。
実のところ、これらのオブジェクトには、独自の特徴とモデルに対する独自の意味がある。これは物事を記述するオブジェクトなのだ。

あるオブジェクトが、ドメインにおける記述的な側面を表現し、概念的な同一性を守らない場合、そういうオブジェクトは値オブジェクトと呼ばれる。
値オブジェクトがインスタンス化される際に表現しようとするのは、何であるかだけが問題と成り、誰であるか、あるいはどれであるかは問われないような設計の要素である。

本書の中に例があったので、そこで何とか理解。

値オブジェクトは、しばしば、オブジェクト間のメッセージでパラメータとして渡される。一過性のことも多く、操作のために生成されては破棄される。
また、エンティティ(および他の値オブジェクト)の属性として使用される。人は同一性のあるエンティティとして、モデル化されるかもしれないが、その人の名前は値オブジェクトである。

あるモデル要素について、その属性しか関心の対象とならないのであれば、その要素を値オブジェクトとして分類すること。値オブジェクトに、自分が伝える属性の意味を表現させ、関係した機能を与えること。値オブジェクトを不変なものとして扱うこと。
同一性を与えず、エンティティを維持するために必要となる複雑な設計を避けること。

エンティティか値オブジェクトかを決めるための説明って感じ。

値オブジェクトを可変であることを許可する方が良い場合

・値が頻繁に変化する場合
・オブジェクトの生成や削除が高くつく場合
・置き換えによって(修正ではなく)クラスタリングが妨げられる場合(前述の例で説明)
・値が共有することがあまりない場合、またはクラスタリングを改良するためや他の技術的理由からそういう共有を見合わせる場合

今後、自分で設計するにあたってのメモ。

・値オブジェクトを含む関連を設計する

値オブジェクト同士の双方向の関連は完全に取り除くように試みること

ここはメモ。

■サービス

時には、単純に「物」とはできないこともある。
場合によっては、設計をできる限り明確にしつつ、実践的に進めて行くと、概念的にどのオブジェクトにも属さないような操作が含まれることがある。
強引に決着をつけるのではなく、問題領域にひかれる自然な輪郭に従って、モデルの中に明確にサービスを含めれば良い。

サービスの冒頭の説明

ドメインから生まれる概念の中には、オブジェクトしてモデル化すると不自然なものもある。
こうしたドメインで必要な機能をエンティティや値オブジェクトの責務として押し付けると、モデルに基づくオブジェクトの定義を歪めるか、意味のない不自然なオブジェクトを追加することになる。

サービスとは、モデルにおいて独立したインターフェースとして提供される操作で、エンティティと値オブジェクトのようには状態をカプセル化しない。
サービスは技術的なフレームワークでは一般的なパターンだが、ドメイン層にも適用できる。

サービスという名前は他のオブジェクトとの関係性を強調している。
エンティティや値オブジェクトとは異なり、純粋にクライアントに対して何が実行できるかという観点から定義されるのだ。
サービスは、実体よりも活動、つまり名詞よりも動詞にちなんで命名される傾向がある。

サービスは節度を持って使用すべきで、エンティティと値オブジェクトから全ての振る舞いを奪ってはならない。
しかし、実際に、操作が重要なドメインの概念なのであれば、サービスはモデル駆動設計の自然な一部を形成する。

サービスとはモデルを持たない振る舞いを持つもので、純粋にクライアントに対して何が実行できるかという観点から定義されるもの。

優れたサービスには3つの特徴がある
1. 操作がドメインの概念に関係しており、その概念がエンティティや値オブジェクトの自然な一部ではない。
2.ドメインモデルの他の要素の観点からインターフェースが定義されている。
3.操作に状態がない。

ここで状態がないというのは、どのクライアントでも得てのサービスのインスタンスを使うにあたって、インスタンスの持つ個々の履歴を気にする必要がない。という意味である。
サービスを実行すると、グルーバルにアクセス可能な情報を使用し、そのグローバルな情報を変更することもあるかもしれません。(つまり副作用がある。)
しかし、ほとんどのドメインオブジェクトが持っているような、オブジェクトの内部にあって、自身の振る舞いに影響を与える状態は、サービスには存在しない。

ドメインにおける重要なプロセスや変換処理が、エンティティや値オブジェクトの自然な責務ではない場合、その操作はサービスとして宣言される独立したインターフェースとしてモデルに追加すること。
モデルの言語を用いて、インターフェースを定義し、操作名が必ずユビキタス言語の一部になるようにすること。
サービスには状態を持たせないこと。

うん、何となくわかってきた。

・サービスと隔離されたドメイン
このパターンは、ドメインにおいてそれ自体が重要な意味を持つサービスに焦点を絞っているが、もちろん、サービスはドメイン層だけで使用されるのではない。
ドメイン層に属するサービスを他のレイヤーから区別することと、その区別をはっきり保つように責務を分解することには、注意が必要である。

多くのドメインサービスやアプリケーションサービスは、エンティティと値オブジェクトで構成される集合体の上に構築され、ドメインに本来備わっている能力をまとめあげて、実際に何らかの処理を行うスクリプトのように振る舞う。

ドメイン層だけで使用されるものではないこともメモ。

・粒度
このパターンの議論では、概念をサービスとしてモデル化することで得られる表現力を強調しているが、このパターンはクライアントをエンティティと値オブジェクトから分離する手段としても、ドメイン層のインターフェースの粒度を制御する手段としても価値がある。

中粒度で状態を持たないサービスは、巨大なシステムで再利用しやすい。
これは、重要な機能をシンプルなインターフェースの後ろにカプセル化しているためだ。
また、細粒度のオブジェクトは、分散システムにおいては、非効率的なメッセージングにつながるかもしれない。

前述した通り、細粒度のドメインオブジェクトを用いると、ドメイン層からアプリケーション層は、ドメインオブジェクトの振る舞いが組み合わされる場所だからだ。
極めて詳細なレベルでの相互作用が複雑であるため、結局アプリケーション層で処理されることになり、ドメインの知識がアプリケーションやユーザインタフェースコードに這い出るのを許して、その知識はドメイン層から失われてしまう。
ドメインサービスを慎重に導入すれば、複数のレイヤ間で境界を鮮明に維持できる。
このパターンでは、クライアントの操作や用途の広さよりも、インタフェースの単純さが優先される。
また、このパターンより、巨大なシステムや分散システムでコンポーネントをパッケージングするのに、非常に便利な中粒度の機能が提供される。
そして、サービスが、ドメインの概念を表現するのに最も自然な方法であることもある。

中粒度で定義されるべきって感じで良いかな?
ここは何度も自分で設計して経験していかないと実感していけない。


■モジュール(パッケージ)

モジュールを選択する際には、システムに関する物語を伝え、概念の凝集した集合を含んでいるものを選ぶこと。
こうすることで、モジュール間は低結合になることが多い。

モジュールには、ユビキタス言語の一部になる名前をつけること。
モジュールとその名前はドメインに対する洞察を反映していなければならない

当たり前のことだけど、低結合、高凝集がキーワード。

大量のリファクタリングを行う開発者でも、プロジェクトの初期に考え出したモジュールで満足する傾向にある。

今後、ここを見直すというのも視野に入れていこう。

・インフラストラクチャ駆動パッケージングの落とし穴
ティア化アーキテクチャーでは、モデルオブジェクトの実装が断片化されるかもしれない。

別々のサーバにコードを分散させようという意図が実際にない限り、単一の概念オブジェクトを実装するコードは全て、同一のオブジェクトにならなくても、同一のモジュールにまとめること。

ドメイン層を他のコードから分離するためにパッケージングを使用すること。そうでなければ、ドメインの開発者にできる限り選択の余地を残し、モデルと設計上の選択をサポートするように、ドメインオブジェクトをパッケージングできるようにすること。

ティア化とは、責務が断片化された状態のこと。
中規模Web開発のためのMVC分割とレイヤアーキテクチャ - Qiita


モデリングパラダイム

オブジェクト指向が主流になっているシステムに、非オブジェクトの要素を混ぜ合わせるための経験則を4つあげよう。
・実装パラダイムと対立しないこと
  ドメインに関する別の考えは常にある。パラダイムに合うモデルの概念を見つけること。
ユビキタス言語に頼ること
ツール間に厳格なつながりがない場合でも、言語を一貫して使用すれば、設計の各部分が分かれて行ってしまうことはない。
UMLにこだわらないこと
UML図のようなツールに固執すると、容易に描けるものに合わせてモデルがゆがめられることがある。
 例えば、UMLには制約を表現する機能が確かにあるが、常にそれで十分とは限らない。
 別の作図スタイル(他のパラダイムでは一般的かもしれない)を選んだり、単純な自然言語で記述したりした方が、オブジェクトの特定の見方を示すことを意図された作図スタイルを、無理に適合させるよりは良い。
・懐疑であること
 ツールは本当に相応の働きをしているか?ルールがあるからといって、ルールエンジンを使うオーバーヘッドが必要とは限らない。
 ルールは多少わかりにくくはなるにしても、オブジェクトとして表現できるが、パラダイムを複数持つことで、事態は極めて複雑になるのだ。

ここはメモ。


脳が疲れる泣

続いて、第2部 モデル駆動設計の構成要素(後半 6章-7章) 頑張ります。

アナリシスパターン―再利用可能なオブジェクトモデル (Object Technology Series)

アナリシスパターン―再利用可能なオブジェクトモデル (Object Technology Series)

  • 作者: マーチンファウラー,Martin Fowler,堀内一,友野晶夫,児玉公信,大脇文雄
  • 出版社/メーカー: ピアソンエデュケーション
  • 発売日: 2002/04
  • メディア: 単行本
  • 購入: 7人 クリック: 89回
  • この商品を含むブログ (70件) を見る
実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

ドメイン駆動 (Programmer’s SELECTION)

ドメイン駆動 (Programmer’s SELECTION)

ビジネスパターンによるモデル駆動設計

ビジネスパターンによるモデル駆動設計

「エリック・エヴァンズのドメイン駆動設計」を読んで(自分用メモ) 第1部 ドメインモデルを機能させる

DDD導入研修の課題として、下記の書籍「エリック・エヴァンズのドメイン駆動設計」を読むことになりました。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

本文章は上記の書籍を引用させて頂きます。


ただ漠然と読んでいるだけだと、頭を通りすぎていきそうなので、自分なりに要点や響いた箇所、メモを残しながら読んでいこうと思います。
「Domain-Drive Design Quickly」は約80ページしかありませんでしたが、こちらはボリュームが大きいので、覚悟しながら読もうと思います。

Kindle書籍からだとコピーできないため、簡単にまとめていきます。

■第1部 ドメインモデルを機能させる

・Intordcution

ドメイン駆動設計におけるモデルの有用性
 1.モデルと設計の確信が相互に形成し合う。
 2.モデルは、チームメンバ全員が使用する言語の基盤である。
 3.モデルとは、蒸留された知識である。

ここも一応メモ。
後ろの章を読むと、より理解度が上がる気がするので。

・第1章 知識を噛み砕く

効率的なモデリングの要素
 1.モデルと実装を結びつける。
 2.モデルにもど突いて言語を洗練させる。
 3.知識豊富なモデルを開発する。
 4.モデルを蒸留する。
 5.ブレインストーミングと実験を行う。

ここの手順はメモとして残しておく。

知識豊富な設計

モデルによって捉えられる知識は「名詞を見つける」ことにとどまらない。
ビジネスの活動やルールも、ドメインに含まれるエンティティと同じように、ドメインにとって中心的なのだ。

エンティティや値を超えて、その先に行こうとする、このような動きに伴った時こそ、知識の嚙み砕きは力を発揮できる。
通常、ドメインエキスパートは、自分の頭の中で起きているプロセスがいかに複雑化を意識することなく、仕事をする中でこれのルールを全て調べて矛盾を調整し、常識で考えて隔たりを埋めている。だが、こういうことはソフトウェアにはできない。
ソフトウェアエキスパートと密接に協力する中で、知識を噛み砕くことによって初めて、ルールが明確となり、具体化されて、折り合いがつけられるか、あるいはスコープの対象外とされるのである。

「名詞を見つける」ことにとどまらないということに少しドキッとした。
ドメインエキスパートとともに知識を噛み砕いて、明確化していくということが大事。

・第2章 コミュニケーションと言語の使い方

声に出してモデリングする

モデルを改良する最適な方法の1つは、話して見ることだ。考えらえるモデルのバリエーションから生じる様々な概念を、声に出して構成してみる。荒削りな表現は聞けば、すぐわかる。

ここは例を見て、そうだなと思った。
実践してみよう。

「彼らには抽象的すぎる。」
「オブジェクトがわかっていない。」
「彼らの用語法に従って要求を集めなければならない。」

豊富な知識を持つドメインエキスパートがモデルを理解できないとしたら、モデルに何か問題があるのだ

ここは現場ではよくありそう。
解決策の一つとして、ユビキタス言語を作っていくというのはあるのか。

設計に関する本質的な詳細は、コードにおいて捉えらえる。

同意。

常に覚えていてほしいのは、モデルは図ではない。

モデルを伝える手段の一つが図であるだけ。

ドキュメントはコードや会話での表現を補わななければならない

そうあるべきだと思う。

すでにコードがうまくやっていること、ドキュメントでもやろうとするべきではない

受託開発でよく作るいわゆる詳細設計書かな。
これらは納品物ということで必要なだけで、いわゆる基本設計書相当のドキュメントがWikiなどに残っていればそれで十分。


・第3章 モデルと実装を結びつける

ソフトウェアシステムの一部を設計する際には、紐付けが明らかになるように、ドメインモデルを文字通りの意味で忠実に反映させること。
モデルについて、再検討し、より自然いソフトウェアに実装されるように修正すること。
これはドメインに対するより深い洞察を反映させようとする時にも言える。強固なユビキタス言語を支えることに加えて、ドメインと実装両方の目的に使える単一のモデルを要求すること

ここは繰り返し記述されていることだが、単一のモデルということに着目。

設計で使用する用語法と責務の基礎的な割り当てをモデルから引き出すこと。
コードはモデルの表現となるから、コードに対する変更はモデルに対する変更になるかもしれない。
その影響は、プロジェクトの他の活動全体へと適宜伝わっていかなけければならない。
実装を一分の狂いもなくモデルに結びつけるには、通常、オブジェクト指向プログラミングのようなモデリングパラダイムをサポートする、ソフトウェア開発のためのツールと言語が必要である。

モデルを元に実装も行っていきましょうという話。
実際問題、コード設計に入った時にトランザクション境界で色々考えなければいけないが、そこは設計パターンみたいな解決のアプローチがあるのかな。

第2部 モデル駆動設計の構成要素 へ続く。

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

ドメイン駆動 (Programmer’s SELECTION)

ドメイン駆動 (Programmer’s SELECTION)

ビジネスパターンによるモデル駆動設計

ビジネスパターンによるモデル駆動設計

Domain-Drive Design Quicklyを読んで(自分用メモ) その1

DDD導入研修の課題として、下記の書籍「Domain-Drive Design Quickly」を読むことになりました。
Domain Driven Design(ドメイン駆動設計) Quickly 日本語版
本文章は上記の日本語訳の資料を引用させて頂きます。


ただ漠然と読んでいるだけだと、頭を通りすぎていきそうなので、自分なりに要点や響いた箇所、メモを残しながら読んでいこうと思います。
※とはいえ、約80ページしかないものではありますが。

DDDの世界への第一歩を踏み出します!

・0.イントロダクション

ソフトウエアの設計は芸術です。したがって芸術と同様、厳密な科学のように
定理や公式を利用して教わったり学んだりできません。私たちはソフトウエア
の作成過程全体に適用できるような、原則や技法を発見することはできます。

芸術。まだこのレベルまでは遥かに至っておりませぬ。。


・1.ドメイン駆動設計とは何か

ドメ インはとても多くの情報を含んでいるので、すべてをモデルに取り込めません。 また、ドメインの大部分は考慮する必要さえないでしょう。こういった取捨選 択自体が大きな課題となります。何をモデルに取り込み、何を捨てるのか。こ れが設計作業であり、ソフトウエアの作成です。銀行業務システムはきっと顧 客の住所録を保持するでしょうが、顧客の目の色は無視します。もっともこれ はわかりやすい例です。他の場合はこれほどわかりやすくないでしょう。

この取捨選択こそが難しいのである。

モデルはソフトウエアの核ですが、これを表現し他人に伝える方法が必要です。 私たちは一人で作業するわけではありません。上手に、正確に、完璧に、曖昧 さを排除して、知識と情報を共有しなければなりません。これにはいくつかの 方法があります。ひとつは図による表現、すなわちダイヤグラム、ユースケー ス、スケッチ、絵などです。もうひとつは記述による表現、つまりドメインに ついての見通しを文章で記述する方法です。そして独自言語で表現する方法も あります。ドメインについての特定の問題を伝達するために私たちは独自の言 語を作成することができますし、そうするベきなのです。これらの詳細は後述 しますが、もっとも大切なのは「モデルを他人に伝えなければならない」という ことです。

「モデルを他人に伝えなければならない」。どのような手段であれ、これが一番大切。

この本はドメイン駆動設計の原則を説明します。この設計原則を適用すれば、
どのような開発過程であれ、その能力を十分に発揮してドメインの複雑な問題
に対して継続的にモデリングし、実装できるようになります。ドメイン駆動設
計は設計と開発を兼ね備え、どのように設計と開発が協調すればよりよいソフ
トウエアが出来上がるのかを示しています。優れた設計は開発を加速させ、開
発からのフィードバックは設計の精度を高めるでしょう。

「優れた設計は開発を加速させ、開発からのフィードバックは設計の精度を高めるでしょう。」
より設計と開発側を結びつけることにより、ソフトウェアの複雑性に立ち向かうということかな。
最近、読んでる下記の書籍で紹介されている原則でも、大まかにはソフトウェアの複雑性に立ち向かうためのものが多い。



・2.ユビキタス言語

ドメイン駆動設計の核となる原則は、ドメインモデルに基づく言語を使うこと
です。ドメインモデルはソフトウエアとドメインが出会う場所にあるのですか
ら、共通言語の基盤として適切です。
共通言語の拠り所としてドメインモデルを使います。そしてこの言語をコミュ
ニケーションに、さらにはコードにも使うようにチームのメンバに要求しまし
ょう。知識を共有しドメインモデルを構築しているあいだにも、チーム内では
レビューをしたり文書や図を作ったりします。どんな形態のコミュニケーショ
ンであれ、常にこの言語で表現しましょう。このような性質からこの言語は
「ユビキタス言語」と呼ばれます。

今までの現場であったような業務に関する共通言語とは正確には違い、ドメインモデルに基づく言語を「ユビキタス言語」とすると。

ユビキタス言語を創造する

この困難な作業に取り組み始めるには、チームのすべてのメンバが共通言語を
作らなければならないと自覚し、重要な点に常に注目するように気をつけ、必
要なときはいつでも作成した共通言語を使うようにします。このような作業の
ときには、独自の専門用語を可能な限り使わないようにします。そしてユビキ
タス言語を使いましょう。ユビキタス言語は明確に、そして正確に意思伝達を
する手助けをしてくれるのですから。

ここは、実際の設計時には特に意識していきたいところ。
いきなり実践ではできないから、ここを何かしらの方法でトレーニングしていきたいな。

もちろん、コードを使ってコミュニケーションをすることも可能です。この方 法はXPプログラミングのコミュニティで広く支持されています。丁寧に書かれ たコードはコミュニケーションにとても適しています。しかし、例えばコード を読むことで、メソッドが表現する振る舞いが理解できても、そのメソッド名 がその振る舞いと同じくらい明瞭に理解できるでしょうか。テストのためのア サーションは、アサーション自体の内容を十分に伝えてくれますが、変数名や コードの構造全体については何か伝えてくれるでしょうか。すべての挙動を一 目瞭然に教えてくれるでしょうか。コードは正しく振る舞いますが、必ずしも 正しい表現をするわけではありません。コードを使ってモデルを表現するのは とても難しいことです。

コメント書きすぎても保守コストが高いので、現実問題どれだけわかりやすくしてもそうだろうなと実感。


3.モデル駆動設計

3-1.モデル駆動設計の基本要素

業務ドメインを中心に据えたソフトウエア開発手法の重要さを強調
しました。ドメインに深く根ざしたモデルをつくることがとても重要であり、
そのモデルは、ドメインの中心にある概念を正確に反映するべきだと説明しま
した。「ユビキタス言語」はモデリングの作業全体を通して、ソフトウエア開
発者とドメインの専門家とのコミュニケーションをとても楽にします。また、
モデルに取り込むべきドメインの中心概念の発見にも役立ちます。モデリング
の目的は、よいモデルを作成することです。そして次は、モデルをコードとし
て実装していく作業です。ソフトウエア開発においては、この作業もモデリン
グと同様に重要です。すばらしいドメインモデルを作成しても、コードの中へ
正確に移植できなければ、品質の悪いソフトウエアになってしまいます。

前章までの振り返り。
頭の整理のためにメモとして残しておく。

どのようなドメインでも様々なモデルで表せます。そしてどのようなモデルで
も様々な方法でコードに落とし込めます。どんな問題にもひとつ以上の解決方
法があります。ではどの方法を選べばいいのでしょうか。よく分析されている
正確なモデルが、必ずしもコードでそのまま表現できるモデルであるとはかぎ
りません。それどころか、ソフトウエア設計の原則を無視した実装になること
もあるでしょう。原則を無視するのは勧められません。重要なのは簡単に、そ
して正確にコードに落とし込めるモデルを選ぶことです。ではここで基本的な
質問です。私たちはどのような手法でモデルからコードへ変換するのでしょう
か。

今までやっていたのは、例えばレシートからモデルを考えるのやった時には、名詞に着目して用語を抜き出してそこから関係性を記述したくらいか。
手法の名前は忘れました。

この方法の主な問題点は、アナリストがモデルの欠点や複雑な点をすべて予見 できないことです。アナリストはモデルの一部の構成要素は詳しく分析しても、 残りは十分に分析していないかもしれません。このため、とても重要な細部が 設計や実装の段階になって初めて見つかってしまいます。仮にドメインを正確 にあらわしたモデルを使ってみれば、オブジェクトの永続化に深刻な問題があ ったり、性能が許容できないほど悪いことがわかるでしょう。

また、開発者は独自に決断をせざるを得ないことがあるでしょう。モデルを作
成した時には考慮していなかった問題を解決するために、設計を変更すること
もあります。モデルから抜け落ちてしまっている部分を設計するのですから、
さらにモデルとの関連性が希薄になります。

分析モデルの欠点の記述。
そもそも、完璧に近いものなんてそうそう難しい。

つまり、より良い方法はドメインモデリングと設計を密接にすることです。モ
デルを作成するときは、ソフトウエアとその設計を考慮するべきです。また開
発者もモデリングに参加すべきです。モデルに基づいた設計作業を後戻りする
ことなく進めるには、ソフトウエアを正確に表現するモデルを選択することが
重要です。コードが基礎になるモデルとしっかり結びついていれば、コードの
意味が明確になり、モデルもより適切になるでしょう。

一つの結論かな。
完璧な設計なんて難しいから設計への手戻りを許容すると。

コードを書く人はモデルをよく知り、モデルが完全であることに責任を感じる
べきです。そして、コードの変更はモデルの変更を伴うことを自覚しなければ
なりません。そうでないと元のモデルと無関係になるまで、コードとリファク
タリングしてしまうでしょう。また、実装に関心を示さないアナリストは、開
発中に初めて見つかった実装上の制限に興味を示さないでしょう。その結果、
モデルは実践に適したものではなくなります。
どんな技術者であれ、ドメインモデルの作成に関係するのであれば、いくらか
時間を割いてコードを触ってみなければなりません。たとえそのプロジェクト
を主導する役割を担っていても技術者であればそうすべきです。コードの変更
に責任がある者はだれでも、コードを通してモデルを表現することを学ばなけ
ればなりません。すべての開発者はドメインモデルについての議論に参加し、
ドメインの専門家と意見交換をする必要があります。プロジェクトのメンバは
様々な方法でドメインモデルの作成に関わりますが、彼らは実際にコードを触
る技術者とユビキタス言語を使って活発に意見を交換しなければなりません。

「モデルをよく知り、モデルが完全であることに責任を感じる。」
他の誰かが立ち上げたプロジェクトに入ることになることがほとんどなので、モデルの理解を深めつつ、修正時にはより気をつかわなければいけないと。

ドメインモデルを正しく設計に反映させるためには、ソフトウエアシステムを
部分ごとに設計します。そうすれば、ドメインモデルと設計の対応関係も明確
になります。また、ドメインモデルを見直して修正を加えることで、より自然
にソフトウエアを実装できるようにします。ドメインの詳細な内容をモデルに
表現しようとする場合も同様です。設計との対応関係が明確なモデルであるこ
と。より自然にソフトウエアを実装できるモデルであること。この二つを満た
すひとつのモデルが必要です。加えて、ユビキタス言語が十分に使われていな
ければなりません。

・「設計との対応関係が明確なモデルであること。」
・「より自然にソフトウエアを実装できるモデルであること。」
この二つを満たすことが必要。

ドメインモデルから設計に使われる用語を抜き出しましょう。また、モデルの
どの要素にどの責務を割り当てるのかも、大まかに考えておきます。コードは
ドメインモデルを表現するので、コードの変更はモデルの変更と同じです。そ
して、その変更の影響はプロジェクトのその他の作業にも、相応に波及してい
かなければなりません。

ここでいわゆるコード設計する形になるのかな。

実装とモデルを強く結びつけるためには、オブジェクト指向プログラミングの
ようなモデリングのパラダイムに基づいているプログラミング言語やソフトウ
エア開発ツールが必要です。
手続き型の言語はモデル駆動設計を十分にサポートしません。モデルの重要な 部分を実装するために必要な概念を提供しないからです。OOPC言語のよう な手続き型言語と同じ使い方ができると言う人もいます。そして実際にOOPの 機能を使えば手続き型言語と同じような使い方もできます。例えば、オブジェ クトをデータ構造とみなして、振る舞いを割り当てないようにします。そして、 振る舞いはファンクションとしてオブジェクトとは別に実装します。しかし、 こうするとデータの意味は開発者にしかわかりません。コードそのものが意味 をはっきりと表現しないからです。手続き型の言語で書かれたプログラムは、 ファンクションの集合として理解されます。プログラムを走らせれば、ひとつ のファンクションが別のファンクションを呼び出しながら処理を進めて結果を 出力します。このようなプログラムでは、概念的に関係のあるプログラムの要 素をカプセル化できません。また、ドメインとコードを対応づけるのも難しい です。
手続き型言語を使って簡単にモデリングし実装できる特定の領域もあります。 例えば数学です。ほとんどの数学理論は計算で表現できるので、ファンクショ ンの呼び出しとデータ構造を使って単純に処理を実行できます。しかし、複雑 なドメインは計算のような抽象的な概念を集めたものではなく、アルゴリズム の集合に単純化できません。したがって、手続き型言語は様々な種類のドメイ ンを表現するには役不足です。モデル駆動設計での手続き型言語の使用は推奨 しません。

「モデル駆動設計での手続き型言語の使用は推奨 しません」とのこと。

モデル駆動設計の基本要素がここから紹介されていくので、一旦別記事で紹介していきます。

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

エリック・エヴァンスのドメイン駆動設計 (IT Architects’Archive ソフトウェア開発の実践)

実践ドメイン駆動設計 (Object Oriented SELECTION)

実践ドメイン駆動設計 (Object Oriented SELECTION)

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その8(Scalaのコレクションライブラリ(immutableとmutable)前半

Scalaを業務で使うことになり、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
https://dwango.github.io/scala_text/index.htmldwango.github.io

可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
Scalaのコレクションライブラリ(immutableとmutable)】
・概要

immutableなコレクションを使うのにはいくつものメリットがあります

関数型プログラミングで多用する再帰との相性が良い
高階関数を用いて簡潔なプログラムを書くことができる
一度作ったコレクションが知らない箇所で変更されていない事を保証できる
並行に動作するプログラムの中で、安全に受け渡しすることができる
mutableなコレクションを効果的に使えばプログラムの実行速度を上げることができますが、mutableなコレクションをどのような場面で使えばいいかは難しい問題です。

この節では、Scalaのコレクションライブラリに含まれる以下のものについての概要を説明します。

Array(mutable)
List(immutable)
Map(immutable)・Map(mutable)
Set(immutable)・ Set(mutable)

メリットがまとめられているのでわかりやすい。
再帰を使いこなせるようになることを、簡潔なプログラムを書けるように勉強していきたい。

・Array

まずは大抵のプログラミング言語にある配列です。

scala> val arr = Array(1, 2, 3, 4, 5)
arr: Array[Int] = Array(1, 2, 3, 4, 5)

これで1から5までの要素を持った配列がarrに代入されました。Scalaの配列は、他の言語のそれと同じように要素の中身を入れ替えることができます。配列の添字は0から始まります。なお、配列の型を指定しなくて良いのは、Array(1, 2, 3, 4, 5)の部分で、要素型がIntであるに違いないとコンパイラ型推論してくれるからです。型を省略せずに書くと

scala> val arr = Array[Int](1, 2, 3, 4, 5)
arr: Array[Int] = Array(1, 2, 3, 4, 5)

となります。ここで、[Int]の部分は型パラメータと呼びます。Arrayだけだとどの型かわからないので、[Int]を付けることでどの型のArrayかを指定しているわけです。この型パラメータは型推論を補うために、色々な箇所で出てくるので覚えておいてください。しかし、この場面では、Arrayの要素型はIntだとわかっているので、冗長です。次に要素へのアクセスと代入です。

scala> arr(0) = 7

scala> arr
res1: Array[Int] = Array(7, 2, 3, 4, 5)

scala> arr(0)
res2: Int = 7

他の言語だとarr[0]のようにしてアクセスすることが多いので最初は戸惑うかもしれませんが、慣れてください。配列の0番目の要素がちゃんと7に入れ替わっていますね。

配列の長さはarr.lengthで取得することができます。

scala> arr.length
res3: Int = 5

Array[Int]はJavaではint[]と同じ意味です。Scalaでは、配列などのコレクションの要素型を表記するとき Collection[ElementType]のように一律に表記し、配列も同じように記述するのです。Javaでは配列型だけ特別扱いするのに比べると統一的だと言えるでしょう。

うん、ここは文法上の話。
使うことになったら、再度確認するくらいで良いかな。

ただし、あくまでも表記上はある程度統一的に扱えますが、実装上はJVMの配列であり、 要素が同じでもequalsの結果がtrueにならない, 生成する際にClassTagというものが必要 などのいくつかの罠があるので、Arrayはパフォーマンス上必要になる場合以外はあまり積極的に使うものではありません。

これは頭に留めておきたい。
ClassTagと言うものについては以下を参照。
Scala ClassTagメモ(Hishidama's Scala ClassTag Memo)

Arrayの練習問題
配列のi番目の要素とj番目の要素を入れ替えるswapArrayメソッドを定義してみましょう。swapArrayメソッドの宣言は

def swapArray[T](arr: Array[T])(i: Int, j: Int): Unit = ???

となります。iとjが配列の範囲外である場合は特に考慮しなくて良いです。

Javaの時にやったような配列の入れ替えの実装。




・Range

Rangeは範囲を表すオブジェクトです。Rangeは直接名前を指定して生成するより、toメソッドとuntilメソッドを用いて呼びだすことが多いです。また、toListメソッドを用いて、その範囲の数値の列を後述するListに変換することができます。では、早速REPLでRangeを使ってみましょう。

scala> 1 to 5
res8: scala.collection.immutable.Range.Inclusive = Range(1, 2, 3, 4, 5)

scala> (1 to 5).toList
res9: List[Int] = List(1, 2, 3, 4, 5)

scala> 1 until 5
res10: scala.collection.immutable.Range = Range(1, 2, 3, 4)

scala> (1 until 5).toList
res11: List[Int] = List(1, 2, 3, 4)

toは右の被演算子を含む範囲を、untilは右の被演算子を含まない範囲を表していることがわかります。また、RangeはtoListで後述するListに変換することができることもわかります。

rangeはGolangにもあるし、JavaだとGuavaのライブラリにもあった気がする。
untilでは含まないということだけ覚えておこう。

・List

さて、導入として大抵の言語にあるArrayを出しましたが、ScalaではArrayを使うことはそれほど多くありません。代わりにListや Vectorといったデータ構造をよく使います(Vectorについては後述します)。Listの特徴は、一度作成したら中身を変更できない(immutable)ということです。中身を変更できないデータ構造(永続データ構造とも呼びます)はScalaがサポートしている関数型プログラミングにとって重要な要素です。それではListを使ってみましょう。

scala> val lst = List(1, 2, 3, 4, 5)
lst: List[Int] = List(1, 2, 3, 4, 5)
scala> lst(0) = 7
<console>:14: error: value update is not a member of List[Int]
       lst(0) = 7
       ^

見ればわかるように、Listは一度作成したら値を更新することができません。しかし、Listは値を更新することができませんが、あるListを元に新しいListを作ることができます。これが値を更新することの代わりになります。以降、Listに対して組み込みで用意されている各種操作をみていくことで、Listの値を更新することなく色々な操作ができることがわかるでしょう。

JavaだとGuavaで使っているImmutableListと同じなので、使う分には問題なし。

Nil:空のList

まず最初に紹介するのはNilです。Scalaで空のListを表すにはNilというものを使います。Rubyなどではnilは言語上かなり特別な意味を持ちますが、Scalaではデフォルトでスコープに入っているということ以外は特別な意味はなく単にobjectです。Nilは単体では意味がありませんが、次に説明する::と合わせて用いることが多いです。

デフォルトでスコープに入っているというのがあまり理解できず。

・:: - Listの先頭に要素をくっつける

(コンスと読みます)は既にあるListの先頭に要素をくっつけるメソッドです。これについては、REPLで結果をみた方が早いでしょう。
scala> val a1 = 1 :: Nil
a1: List[Int] = List(1)

scala> val a2 = 2 :: a1
a2: List[Int] = List(2, 1)

scala> val a3 = 3 :: a2
a3: List[Int] = List(3, 2, 1)

scala> val a4 = 4 :: a3
a4: List[Int] = List(4, 3, 2, 1)

scala> val a5 = 5 :: a3
a5: List[Int] = List(5, 3, 2, 1)

付け足したい要素を::を挟んでListの前に書くことでListの先頭に要素がくっついていることがわかります。ここで、::はやや特別な呼び出し方をするメソッドであることを説明しなければなりません。まず、Scalaでは1引数のメソッドは中置記法で書くことができます。それで、1 :: Nil のように書くことができるわけです。次に、メソッド名の最後が:で終わる場合、被演算子の前と後ろをひっくり返して右結合で呼び出します。たとえば、

scala> 1 :: 2 :: 3 :: 4 :: Nil
res13: List[Int] = List(1, 2, 3, 4)

は、実際には、

scala> Nil.::(4).::(3).::(2).::(1)
res14: List[Int] = List(1, 2, 3, 4)

のように解釈されます。Listの要素が演算子の前に来て、一見数値のメソッドのように見えるのにListのメソッドとして呼び出せるのはそのためです。

Listの後ろにくっつけるのではなく、先頭にくっつけるメソッドの説明が先なのはNilの説明の都合上かな?

・++:List同士の連結

    1. はList同士を連結するメソッドです。これもREPLで見た方が早いでしょう。
scala> List(1, 2) ++ List(3, 4)
res15: List[Int] = List(1, 2, 3, 4)

scala> List(1) ++ List(3, 4, 5)
res16: List[Int] = List(1, 3, 4, 5)

scala> List(3, 4, 5) ++ List(1)
res17: List[Int] = List(3, 4, 5, 1)
    1. は1引数のメソッドなので、中置記法で書いています。また、末尾が:で終わっていないので、たとえば、
scala> List(1, 2) ++ List(3, 4)
res18: List[Int] = List(1, 2, 3, 4)

scala> List(1, 2).++(List(3, 4))
res19: List[Int] = List(1, 2, 3, 4)

と同じ意味です。大きなList同士を連結する場合、計算量が大きくなるのでその点には注意した方が良いです。

計算量は注意しよう。

・mkString:文字列のフォーマッティング

このメソッドはScalaで非常に頻繁に使用され皆さんも、Scalaを使っていく上で使う機会が多いであろうメソッドです。このメソッドは引数によって多重定義されており、3バージョンあるのでそれぞれを紹介します。

mkString

引数なしバージョンです。このメソッドは、単にListの各要素を左から順に繋げた文字列を返します。

scala> List(1, 2, 3, 4, 5).mkString
res20: String = 12345

注意しなければならないのは、引数なしメソッドのmkStringは()を付けて呼びだすことができない という点です。たとえば、以下のコードは、若干分かりにくいエラーメッセージがでてコンパイルに失敗します。

scala> List(1, 2, 3, 4, 5).mkString()
<console>:13: error: overloaded method value mkString with alternatives:
  => String <and>
  (sep: String)String <and>
  (start: String,sep: String,end: String)String
 cannot be applied to ()
       List(1, 2, 3, 4, 5).mkString()

Scalaの0引数メソッドは()なしと ()を使った定義の二通りあって、前者の形式で定義されたメソッドは()を付けずに呼び出さなければいけません。逆に、()を使って定義されたメソッドは、()を付けても付けなくても良いことになっています。このScalaの仕様は混乱しやすいので注意してください。

これは確かにはまりそう。Scalaの仕様としても覚えておこう。

mkString(sep: String)

引数にセパレータ文字列sepを取り、Listの各要素をsepで区切って左から順に繋げた文字列を返します。

scala> List(1, 2, 3, 4, 5).mkString(",")
res22: String = 1,2,3,4,5

mkString(start: String, sep: String, end: String)

mkString(sep)とほとんど同じですが、startとendに囲まれた文字列を返すところが異なります。

scala> List(1, 2, 3, 4, 5).mkString("[", ",", "]")
res23: String = [1,2,3,4,5]

ここは問題なし。

・mkstringの練習問題

mkStringを使って、最初の数startと最後の数endを受け取って、

start,...,end

となるような文字列を返すメソッドjoinByCommaを定義してみましょう(ヒント:Range にもmkStringメソッドはあります)。

def joinByComma(start: Int, end: Int): String = {
  (start to end).mkString

}

ここも問題なし。

・foldLeft:左からの畳み込み

foldLeftメソッドはListにとって非常に基本的なメソッドです。他の様々なメソッドをfoldLeftを使って実装することができます。foldLeftの宣言をScalaAPIドキュメントから引用すると、

def foldLeft[B](z: B)(f: (B, A) ⇒ B): B

となります。zがfoldLeftの結果の初期値で、リストを左からたどりながらfを適用していきます。foldLeftについてはイメージが湧きにくいと思いますので、List(1, 2, 3).foldLeft(0)((x, y) => x + y)の結果を図示します。

       +
      / \
     +   3
    / \
   +   2
  / \
 0   1

この図で、

  +
  / \
 0   1

は+に0と1を与えて適用するということを意味します。リストの要素を左から順にfを使って「畳み込む」(fold は英語で畳み込むという意味を持ちます)状態がイメージできるでしょうか。foldLeftは汎用性の高いメソッドで、たとえば、Listの要素の合計を求めたい場合は

scala> List(1, 2, 3).foldLeft(0)((x, y) => x + y)
res25: Int = 6

Listの要素を全て掛けあわせた結果を求めたい場合は

scala> List(1, 2, 3).foldLeft(1)((x, y) => x * y)
res26: Int = 6

とすることで求める結果を得ることができます1。その他にも様々な処理をfoldLeftを用いて実装することができます。

うん、ここはUndersocre.jsで勉強した時にふわっとやったので覚えてる。
使いこなせるよう問題をいくつか解きたい。

foldLeftの練習問題

foldLeftを用いて、Listの要素を反転させる次のシグニチャを持ったメソッドreverseを実装してみましょう:

def reverse[T](list: List[T]): List[T] = list.foldLeft(Nil: List[T])((a, b) => b :: a)

OK。



・foldRight:右からの畳み込み

foldLeftがListの左からの畳み込みだったのに対して、foldRightは右からの畳込みです。foldRightの宣言を ScalaAPIドキュメントから参照すると、

def foldRight[B](z: B)(op: (A, B) ⇒ B): B

となります。foldRightに与える関数であるopの引数の順序がfoldLeftの場合と逆になっている事に注意してください。 foldRightをList(1, 2, 3).foldRight(0)((y, x) => y + x)とした場合の様子を図示すると次のようになります

  +
  / \
 1   +   
    / \
   2   +   
      / \
     3   0

ちょうどfoldLeftと対称になっています。foldRightも非常に汎用性の高いメソッドで、多くの処理をfoldRightを用いて実装することができます。

foldLeftとの使い分けがそこまでわからず。


foldRightの練習問題 その1

Listの全ての要素を足し合わせるメソッドsumをfoldRightを用いて実装してみましょう。sumの宣言は次のようになります。なお、Listが空のときは0を返してみましょう。

scala>  def sum(list: List[Int]): Int = list.foldRight(0)((a,b) => a+b)

すんなりいけたので、問題なし。


foldRightの練習問題 その2

Listの全ての要素を掛け合わせるメソッドmulをfoldRightを用いて実装してみましょう。mulの宣言は次のようになります。なお、Listが空のときは1を返してみましょう。

scala> def mul(list: List[Int]): Int = list.foldRight(1)((a,b) =>a * b)
mul: (list: List[Int])Int

これもすんなりいけたので、問題なし。

foldRightの練習問題 その3

mkStringを実装してみましょう。mkStringそのものを使ってはいけませんが、foldLeftやfoldRightなどのListに定義されている他のメソッドは自由に使って構いません。ListのAPIリファレンス を読めば必要なメソッドが載っています。実装するmkStringの宣言は

scala>def mkString[T](list: List[T])(sep: String): String = list match {
  case Nil => ""
  case x::xs => xs.foldLeft(x.toString){(x, y) => x + sep + y}
}

ここは解けず。
回答例を参考にする。
x:xsがListを表すことができて、case matchで引っかかるそう。
9.1 リストの使用 (Using Lists) - プログラミング言語Scala 日本語情報サイト


引き続き頑張る。

Scalaスケーラブルプログラミング第3版

Scalaスケーラブルプログラミング第3版

Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門

ドワンゴの新卒エンジニア向けの研修資料でScalaに入門 その7(関数)

関数型のパラダイムを学んで業務に活かそうということで、以下のドワンゴオリジナルの新卒エンジニア向けの研修資料でScalaを本格的に勉強してみることにした。
dwango.github.io
※Java8でごにょごにょしないのは、Scalaを趣味でやりたいという意図があるだけです。

どうせなら、Scalaの言語仕様からガッツリ学んで自分のものにしたい。
また、可能な限りGolangとの比較も入れていきます。

学んだことをとにかく走り書きしていきます。
【関数】
Scalaの関数

Scalaの関数は、他の言語の関数と扱いが異なります。Scalaの関数は単に Function0 〜 Function22 までのトレイトの無名サブクラスのインスタンスなのです。

たとえば、2つの整数を取って加算した値を返すadd関数は次のようにして定義することができます

scala> val add = new Function2[Int, Int, Int]{
     |   def apply(x: Int, y: Int): Int = x + y
     | }
add: (Int, Int) => Int = <function2>

scala> add.apply(100, 200)
res0: Int = 300

scala> add(100, 200)
res1: Int = 300

Function0からFunction22までの全ての関数は引数の数に応じたapplyメソッドを定義する必要があります。 applyメソッドはScalaコンパイラから特別扱いされ、x.apply(y)は常にx(y)のように書くことができます。後者の方が関数の呼び方としては自然ですね。

また、関数を定義するといっても、単にFunction0からFunction22までのトレイトの無名サブクラスのインスタンスを作っているだけです。

関数の作り方は理解。
確かもっと簡単に作れていた印象。
省略しないで書くと、このような処理をしているのか。
トレイトの無名サブクラスのインスタンス
無名サブクラスと言われてもピンとこない。。

・無名関数

前項でScalaで関数を定義しましたが、これを使ってプログラミングをするとコードが冗長になり過ぎます。そのため、 ScalaではFunction0〜Function22までのトレイトのインスタンスを生成するためのシンタックスシュガーが用意されています。たとえば、先ほどのadd関数は

scala> val add = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = <function2>

と書くことができます。ここで、addには単に関数オブジェクトが入っているだけであって、関数本体には何の名前も付いていないことに注意してください。この、addの右辺のような定義をScalaでは無名関数と呼びます。無名関数は単なるFunctionNオブジェクトですから、自由に変数や引数に代入したり返り値として返すことができます。このような、関数を自由に変数や引数に代入したり返り値として返すことができる性質を指して、Scalaでは関数が第一級の値(First Class Object)であるといいます。

無名関数の一般的な構文は次のようになります。

(n1: N1, n2: N2, n3: N3, ...nn: NN) => B

n1からnnまでが仮引数の定義でN1からNNまでが仮引数の型です。Bは無名関数の本体です。無名関数の返り値の型は通常は Bの型から推論されます。先ほど述べたように、Scalaの関数はFunction0〜Function22までのトレイトの無名サブクラスのインスタンスですから、引数の最大個数は22個になります。

JavaScriptの無名関数と同じイメージ。
関数が「First Class Object」であるというのはこういうことか。
ちなみに、Golangも関数は「First Class Object」です。
また、Golangジェネリクスがないので、リフレクションで頑張らないと関数型の汎用的なmap関数やreduce関数のようなことはできないのです。

・関数の型

このようにして定義した関数の型は、本来はFunctionN[...]のようにして記述しなければいけませんが、関数の型については特別にシンタックスシュガーが設けられています。一般に、

(n1: N1, n2: N2, n3: N3, ...nn: NN) => B

となるような関数の型はFunctionN[N1, N2, N3, ...NN, Bの型]と書く代わりに

(N1, N2, N3, ...NN) => Bの型

として記述することができます。直接FunctionNを型として使うことは稀なので、こちらのシンタックスシュガーを覚えておくと良いでしょう。

具体例のサンプルが欲しかった。
シンタックスシュガーの記述に関しては後ほど、コップ本で調べる。

・関数のカリー化

関数型言語ではカリー化というテクニックがよく使われます。カリー化とは、たとえば (Int, Int) => Int 型の関数のように複数の引数を取る関数があったとき、これを Int => Int => Int 型の関数のように、1つの引数を取り、残りの引数を取る関数を返す関数のチェインで表現するというものです。試しに上記のaddをカリー化してみましょう。

scala> val add = (x: Int, y: Int) => x + y
add: (Int, Int) => Int = <function2>

scala> val addCurried = (x: Int) => ((y: Int) => x + y)
addCurried: Int => (Int => Int) = <function1>

scala> add(100, 200)
res2: Int = 300

scala> addCurried(100)(200)
res3: Int = 300

無名関数を定義する構文をネストさせて使っているだけで、何も特別なことはしていないことがわかります。

また、Scalaではメソッドの引数リストを複数に分けることで簡単にカリー化された関数を得ることができます。このことをREPLを用いて確認してみましょう。

scala> def add(x: Int, y: Int): Int = x + y
add: (x: Int, y: Int)Int

scala> add _
res0: (Int, Int) => Int = <function2>

scala> def addCurried(x: Int)(y: Int): Int = x + y
addCurried: (x: Int)(y: Int)Int

scala> addCurried _
res1: Int => (Int => Int) = <function1>

引数リストを複数に分けたaddCurriedから得られた関数は1引数関数のチェインになっていて、確かにカリー化されています。

Scalaのライブラリの中にはカリー化された形式の関数を要求するものがあったりするので、とりあえず技法として覚えておくのが良いでしょう。

メソッドの引数リストを複数に分けることで簡単にカリー化された関数を得ることができるのか。
Underscore.jsではそこまで便利ではなかったかなー
以下の書籍で少しだけ関数型の勉強はしたことがあったが、途中で挫折して積読になってしまっていた。。

JavaScriptで学ぶ関数型プログラミング

JavaScriptで学ぶ関数型プログラミング

また、Golangではシンタックスシュガーは用意されていなかった。
以下の記事でカリー化のサンプルがあった。
Go言語で関数のカリー化(currying)入門 - Qiita

・メソッドと関数の違い

メソッドについては既に説明しましたが、メソッドと関数の違いについてはScalaを勉強する際に注意する必要があります。本来はdefで始まる構文で定義されたものだけがメソッドなのですが、説明の便宜上、所属するオブジェクトの無いメソッド(今回は説明していません)やREPLで定義したメソッドを関数と呼んだりすることがあります。書籍やWebでもこの2つを意図的に、あるいは無意識に混同している例が多々あるので(Scalaバイブル『Scalaスケーラブルプログラミング』でも意図的なメソッドと関数の混同の例がいくつかあります)注意してください。

再度強調すると、メソッドはdefで始まる構文で定義されたものであり、それを関数と呼ぶのはあくまで説明の便宜上であるということです。ここまでメソッドと関数の違いについて強調してきましたが、それは、メソッドは第一級の値ではないのに対して関数は第一級の値であるという大きな違いがあるからです。メソッドを取る引数やメソッドを返す関数、メソッドが入った変数といったものはScalaには存在しません。

ここはしっかりと押さえたいところ。

高階関数

関数を引数に取ったり関数を返すメソッドや関数のことを高階関数と呼びます。先ほどメソッドと関数の違いについて説明したばかりなのに、メソッドのことも関数というのはいささか奇妙ですが、慣習的にそう呼ぶものだと思ってください。

早速高階関数の例についてみてみましょう。

scala> def double(n: Int, f: Int => Int): Int = {
     |   f(f(n))
     | }
double: (n: Int, f: Int => Int)Int

これは与えられた関数fを2回nに適用する関数doubleです。ちなみに、高階関数に渡される関数は適切な名前が付けられないことも多く、その場合はfやgなどの1文字の名前をよく使います。他の関数型プログラミング言語でも同様の慣習があります。呼び出しは次のようになります。

scala> double(1, m => m * 2)
res4: Int = 4

scala> double(2, m => m * 3)
res5: Int = 18

scala> double(3, m => m * 4)
res6: Int = 48

最初の呼び出しは1に対して、与えられた引数を2倍する関数を渡していますから、1 * 2 * 2 = 4になります。2番めの呼び出しは2に対して、与えられた引数を3倍する関数を渡していますから、2 * 3 * 3 = 18になります。最後の呼び出しは、3に対して与えられた引数を4倍する関数を渡していますから、3 * 4 * 4 = 48になります。

上記のようなサンプルだと、累乗計算を始め計算系の処理が楽にできそうなイメージ。
高階関数に渡される関数の名前が適切ではない1文字の名前が渡されるのは覚えておこう。

もう少し意味のある例を出してみましょう。プログラムを書くとき、

初期化
何らかの処理
後始末処理
というパターンは頻出します。これをメソッドにした高階関数aroundを定義します。

scala> def around(init: () => Unit, body: () => Any, fin: () => Unit): Any = {
     |   init()
     |   try {
     |     body()
     |   } finally {
     |     fin()
     |   }
     | }
around: (init: () => Unit, body: () => Any, fin: () => Unit)Any

try-finally 構文は、後の例外処理の節でも出てきますが、大体Javaのそれと同じだと思ってください。このaround関数は次のようにして使うことができます。

scala> around(
     |   () => println("ファイルを開く"),
     |   () => println("ファイルに対する処理"),
     |   () => println("ファイルを閉じる")
     | )
ファイルを開くファイルに対する処理ファイルを閉じる
res7: Any = ()

aroundに渡した関数が順番に呼ばれていることがわかります。ここで、bodyの部分で例外を発生させてみます。throwはJavaのそれと同じで例外を投げるための構文です。

scala> around(
     |   () => println("ファイルを開く"),
     |   () => throw new Exception("例外発生!"),
     |   () => println("ファイルを閉じる")
     | )
ファイルを開くファイルを閉じる
java.lang.Exception: 例外発生!
  at $anonfun$3.apply(<console>:16)
  at $anonfun$3.apply(<console>:16)
  at .around(<console>:15)
  ... 906 elided

のそれぞれを部品化して、「何らかの処理」の部分で異常が発生しても必ず後始末処理を実行できています。このaroundメソッドは1〜3の手順を踏む様々な処理に流用することができます。一方、1〜3のそれぞれは呼び出し側で自由に与えることができます。このように処理を値として部品化することは高階関数を定義する大きなメリットの1つです。

ちなみに、Java 7では後始末処理を自動化するtry-with-resources文が言語として取り入れられましたが、高階関数のある言語では、言語に頼らず自分でそのような働きをするメソッドを定義することができます。

後のコレクションの節を読むことで、高階関数のメリットをより具体的に理解できるようになるでしょう。

高階関数の使いどころをよくわかっていなかったけど、このような形で使うこともできるんだ。


Programming in Scala: Updated for Scala 2.12

Programming in Scala: Updated for Scala 2.12

Scalaパズル 36の罠から学ぶベストプラクティス

Scalaパズル 36の罠から学ぶベストプラクティス

SCALAプログラミング入門

SCALAプログラミング入門