ラベル Scala の投稿を表示しています。 すべての投稿を表示
ラベル Scala の投稿を表示しています。 すべての投稿を表示

2019年2月11日月曜日

オブジェクト指向エクササイズの陳腐化

オブジェクト指向エクササイズというのが一頃流行った。自分も Java プログラマだったころは、1,000〜2,000行ほどのプログラムに適用して試したり、同僚にも勧めたりしたことがある。

ただ今の時代、パラダイム面でオブジェクト指向一強だった昔とは違うし、自分でもフルタイムで関数型プログラミングをやるようになったりして、改めて見直してみるとオブジェクト指向エクササイズも色々と怪しいところが多い。

この記事では関数型の視点も取り入れつつ再評価してみたい。

■ 評価の観点
オブジェクト指向エクササイズは『ThoughtWorks アンソロジー』所収のエッセイで紹介されたもので、著者によればオブジェクト指向の考え方を磨くのに役立つという9つのルールから成る。

原語だと Object Calisthenics と言うが、Calisthenics とは「懸垂、腕立て伏せ、ストレッチ、ジャンピングジャックのような、健康状態や体型を維持・改善するための器具を使わない運動(Vocaburary.com)」の事を指す。カタカナでエクササイズというと練習問題のような語感もあるが、もともとは体育・スポーツ・健康管理に関連する言葉らしい。

メタファーの背景にこうした語義があることを踏まえつつ、次の4段階で9つのルールを見直してみる。
  • 有益: 練習効果だけではなく、実プロジェクトでもルールやガイドラインとして取り入れてみる価値のあるもの。野球の素振りや格闘技の技の練習など、本番で確実に再現するための反復練習にも似たもの。
  • 練習としては有効: 上述の Calithenics の語義にまさに近く、著者が意図している通りのもの。実戦でそのまま使う所作でははないが、基礎体力育成のための筋トレやストレッチのような効果が期待できるエクササイズ。
  • ほぼ無意味: あえて実施してみたところで、せいぜい、やはり意味はなかったと納得するしかなさそうなルール。時間の無駄なので、なにか他の勉強でもしていた方が吉なもの。
  • 有害: むかーしの運動部では、ウサギ跳びや練習中の水飲み禁止など、今では医学的に否定されている練習や慣行が奨励されていた。それらと同じように効果が疑わしいどころか、むしろ悪い癖がついて不健康にすらなりそうなプラクティス。
以下、それぞれ見てみる。


■ 1. Only One Level Of Indentation Per Method
〜 一つのメソッドにつきインデントは1段階までにすること 〜

これは有益で、実プロジェクトでもある程度取り入れる価値がある。

エッセイでは SRP(単一責任原則)や短いメソッド(3行のメソッド)に関連して解説されているが、実は SLAP(Single Level of Abstraction Principles)にも関係が深い。

Kent Beck が 『Smalltalk Best Practice Patterns』で紹介していたComposed Methodにも関連するが、抽象度を適切にそろえて構成したメソッドは、5行前後の1段階のコードブロックで表現できるというもの。

これはオブジェクト指向ではなくても言えることで、Scala だと特に、for comprehension のネストは、抽象レベルが混在している可能性が高いので普段から避けたい。


■ 2. Don’t Use The ELSE Keyword
〜 ELSE 句を使用しないこと 〜
これはほぼ無意味

たとえば、下の式は連続一様分布の関数を普通の数式として表したものだけど、これを自然にコーディングすると当然 ELSE 句が生じる。これを ELSE句を使わずに書いてもかえって不自然すぎというか間違ってるので練習としての意味もない。
\[y = \begin{cases} \frac{1}{b - a}, & \text{for } x \in [a, b] \\ 0, & \text{otherwise} \end{cases} \]
また、エッセイでは IF 〜 ELSE を三項演算子で置き換えているサンプルもあるが、三項演算子は式だから OKということなら Scala で IF 文を式として扱えば、ELSE 句を禁止できないことになり、そもそもの基準があやふやになる。

あと、いつものオブジェクト指向のアレで、やたらとポリモーフィズムを連呼して Strategy パターンや NullObject パターンを推奨しているが、せっかく代数的データ型とパターンマッチで書いたシンプルなコードを、ムダな間接層を伴うパターンで置き換えてもまともな知恵は何一つ得られない。やってみてがっかりしても害にはならないが時間の無駄。


■ 3. Wrap All Primitives And Strings
〜 すべてのプリミティブ型とストリング型をラップすること 〜

これは有益。エクササイズに留めず、実戦のコーディングやレビュー観点に取り入れてみる価値があると思う。

もっと言えば、昔の Java ではラップするだけで精一杯だったかもしれないが、現代の Scala プログラミングではジェネリクスや型レベルプログラミングや依存型など、のパワーを追求する道具立てが昔より豊富なので、どんどん活用すると良いと思う。


■ 4. First Class Collections
〜 ファーストクラスコレクションを使用すること 〜

これはほぼ無意味。オブジェクト指向では List は準プリミティブなコンテナオブジェクトに過ぎないのだろうけど、関数型ではあえてドメイン固有の要素から切り離した普遍的な代数的性質なのだから(内部実装はともかく)、せっかく導出した抽象構造をラップして元に戻すのは色々逆行することになり不毛。

また「0または1以上」の多重度をファーストクラスコレクションにするなら、「0 または 1」でも同じことが成り立つはずで、だとすると Option もダメってことになるが、Option[Name] を MaybeName にラップする不毛さはすぐ分かると思う。さらに関数の定義域や値域に使うのもダメとなれば、自然なパターンマッチすらもできなくなる(無理して unapply を書けなくもないが無意味)し、言語が提供する利点を無駄に捨てているだけで、練習としての効果もない。

普通に Option、List、NonEmptyList(Catsなどの)、Sized(Shapelessの) などで表現した方が端的に表現できるデータの性質を、オブジェクト指向ではどうだからといった理由だけでむやみにラップしても何も良いことがない。

カプセル化の観点で見ても、参照透過でイミュータブルなコードを書いていれば、コレクション内部の値を外から勝手に変更することはできず「隠蔽」の価値は半減するので、無駄なラッパー層のコストに釣り合うメリットはやはり無い。


■ 5. One Dot Per Line
〜 1行につきドットは1つまでにすること 〜

ドットの連鎖で生じるいくつかのオブジェクトの責務を、「仲介者」が持ってしまうことになる状況を、単一責任原則デメテルの法則に則って防ぎたい、あるいはその発想を身にけたいって主旨だが、、、ほぼ無意味

ドットの連鎖禁止でできなくなることには、以下のようなものがあるが、、、
  • List や Stream を、map して filter して reduce するような計算
  • DDD でいう Closure of Operations (例えば数値計算の演算子もメソッド呼び出しだと考えれば、a + b はOKでも a + b + c は a.plus(b).plus(c) だからアウト。三角形の面積すら計算できない。)
  • 関数や計算の合成
  • fluent interface な EDSL
「オモチャのオモチャ」で遊ぶのをやめさせるための縛りとしてドットの連鎖に着目するのは、普及期ごろの Java ではそれで良かったかもしれないが、昨今のプログラミング技法を鑑みると時代遅れだし、ましてや関数型プログラミングでは的ハズレすぎてなんの学びも得られない。

※ドットで改行しても同じことだから、「一行あたりのドット」というよりドットの連鎖の禁止として考えている。


■ 6. Don’t Abbreviate
〜 名前を省略しないこと 〜

クラス名、メソッド名、フィールド名を省略しないことには別に異存はない。というか別にあえて禁止するまでもなく普通そうなのだから、練習としては毒にも薬にもならない。ただし逆にローカル変数の命名では、一文字に省略しても可読性が落ちないようにスコープを工夫したほうが良いので、むしろ有害の可能性もある。

つまり省略により可読性が落ちるしたら、関数が大きすぎて今読んでいる行とローカル変数の宣言行が離れすぎているために型が視界の外に出てしまっているとか、あるいは変数が多すぎて紛らわしくなっているとかなので、スコープをコンパクトにすれば済む。むしろそうする方が、名前をフルスペルにする事なんかより遥かにコード改善効果がある。

そもそも本当に命名の省略が常に可読性を劣化させるなら、数学や物理の公式もフルスペルの変数や記号で書かれてるはずだろうが、そうはなってないし、それどころか全部フルスペルにしたら逆に可読性が落ちて読みにくいことくらい少し想像すればわかる。

さらに関数型だと抽象度が高いコードも増えて、具体的な名前をつけようがない f、g、h としか呼べない関数を操作することが頻繁にある。ジェネリクスの型パラメータの名前だって、フルスペルにしたら逆に読みにくい。

大昔は「1文字変数名はループの i と j のみしか許さない」みたいなルールがあったものだけど、本当はクソ長いコードでループや条件ブロックを入れ子にしてるから可読性が落ちてただけで、直す場所が違う。


■ 7. Keep All Entities Small
〜 全てのエンティティを小さくすること 〜

これは有益。プログラムの構成要素について「いくら何でもこれ以上大きくなったら異常」という上限は、「Rule of 30」あたりを目安にすれば良いだろうけど、ちょうどよい小ささの発見は対象とスキル次第になる。

ただし Scala で上手く関数型プログラミングすれば、従来の Java よりは小さくなることはほぼ確実なので、『FP in Scala』などを参考にしながら、オブジェクト指向エクササイズというより関数型エクササイズとして練習すれば良いと思う。


■ 8. No Classes With More Than Two Instance Variables
〜 一つのクラスにつきインスタンス変数は2つまでにすること 〜

実プロジェクトのルールには使えないが練習としては有効かもしれない。

このルールを適用すると全ての複合型を2分木状の型構成で表現することになるけど、3要素以上で構成するのが自然な型も普通に多い。たとえば、AWS EC2 の `describe instances` コマンドの Output を2分木で表現することを想像すると、ひどく不自然になるのはすぐ分かる。だから 2という縛りは飽くまでトレーニング用の人工的な制約でしかない。

とはいえ、スポーツなどでも実戦にはない負荷をかけたり人工的に作った状況下での練習に効果があるように、このエクササイズも一度やってみる価値はあるかもしれない。ちなみに関数型な Scala で練習するとしたら、case class 等の Product Type だけではなく、sealed trait と case object で構成した Sum Type にもこのルールを適用すると、良いモデリング練習になると思う。


■ 9. No Getters/Setters/Properties
〜 Getter/Setter/プロパティを使用しないこと 〜

ほぼ無意味。代数的データ型の Product Type に「Tell, Don't Ask」を適用するのはナンセンス。むしろ無駄な責務の肥大化を招くだけ。

例えば、エッセイに Name クラスがサンプルとして載っているが、これを文字列に書式化したり Json に変換したりする場合、メソッドを Name に持たせるのは責務の置き場所が違う。`Visitor` パターンなどを使えばできるかもしれないが、コスト以上のメリットも得られず無駄な複雑さを招くだけ。

また例えば、数値と単位をそれぞれフィールドとしてもつ体重クラスと身長クラスから BMI を求めるコードを考えると、フィールドにアクセスできない以上クラスの外では計算できないし、かといってBMIの計算は体重の責務でも身長の責務でもないし、すぐに詰んでしまう。

「Tell, Don't Ask」を適用できるある種の文脈もないことはないが(特にOOPでは)、これを一般化してプログラム全体に適用するには無理がある。ましてや関数型プログラミングの場面では、よほど暇ならばあえて実験してみてやはり意味無しという結果を得るのも一興かもしれないけど、不自然なプログラムを書く練習にしかならないから不毛。


■ 結論
関数型からみた9つのルールの再評価結果
  • 有益: 1, 3, 7 の 3個
  • 練習としては有効: 8 の 1個
  • ほぼ無意味: 2, 4, 5, 9 の 4個
  • 有害: 6 の 1個
「無意味」と「有害」で過半数。残った「有益」と「有効」も、別にオブジェクト指向に限った話ではなく、「プログラミングエクササイズ」でも「関数型エクササイズ」でも成り立つし、なんなら「ベターな手続き型プログラムを書くための練習ルール」としても採用できる普通の話だった。まあ Java とか、"Better Java" 程度の Scalaプログラミングで精一杯の感じのあれなら、やってみても良いかもしれない。

■ 補足
オブジェクト指向について
オブジェクト指向について語りだすと、真の起源はどうだったとか、いや実は分派があってとか面倒だが、ここでは wikipedia に載ってるシンプルなパラダイムの分類で考えている。

┌ 命令型┬─ 手続き型
│       └─ オブジェクト指向
└ 宣言型┬─ 関数型
         └─ 論理型
一般にオブジェクト指向と関数型を対立概念と据えて語られることが多いが、こうして見てみると、COBOL や C や Java や Smalltalk みたいな命令型の一群と、Haskell や Prolog のような宣言型の一群で、大きく別れていることになる。昔から繰り返されてきたオブジェクト指向勢による手続き型叩きが(正直、自分も心当たりあるが)、一段上の観点で見直すと、命令型の枠内での内紛でしかなかったことが分かる。

2014年4月21日月曜日

Heroku/Play2.2/Scala で Hello, World!

初めて Play と heroku を触って、とりあえず疎通確認までやってみたいってとき、現時点(2014/04/20)のプロダクトでちゃんと動くシンプルな getting started 的な資料が、なかなか見当たらない。

heroku のドキュメントを見ると、Scala 版は Play を使っていないし、Java 版だと Play用の記事はあるもののバージョンが古いので、play newのときに Scala を選択して後は指示通りって感じだと、やっぱりちゃんと動かない。

Play のサイト(日本語版)の方では、scala 用のドキュメントがあるけど、Play 2.2.x じゃなく Play 2.1.5 なので、このままだと動かない。英語版では、一応、2.2.x 用に修正されているようだけど、PostgreSQLとの連携とか後でもいいから heroku との連携だけ確認したいってニーズにはやや冗長。

つうわけで、手短に heroku 上で Play(scala) の HelloWorld を動かしてみる手順をまとめてみた。

============================

■ まず Play プロジェクトを作る。
$ play new helloworld
       _
 _ __ | | __ _ _  _
| '_ \| |/ _' | || |
|  __/|_|\____|\__ /
|_|            |__/

play 2.2.2 built with Scala 2.10.3 (running Java 1.7.0_51), http://www.playframework.com

The new application will be created in /home/xad/devel/workspace/scala/play1/helloworld

What is the application name? [helloworld]
> 

Which template do you want to use for this new application? 

  1             - Create a simple Scala application
  2             - Create a simple Java application

> 1
OK, application helloworld is created.

Have fun!
プロジェクトが生成されるので、以降、helloworld フォルダの中で作業する。フォルダの中身は以下のような感じ。
$ cd helloworld/
$ tree
.
├── README
├── app
│   ├── controllers
│   │   └── Application.scala
│   └── views
│       ├── index.scala.html
│       └── main.scala.html
├── build.sbt
├── conf
│   ├── application.conf
│   └── routes
├── project
│   ├── build.properties
│   └── plugins.sbt
├── public
│   ├── images
│   │   └── favicon.png
│   ├── javascripts
│   │   └── jquery-1.9.0.min.js
│   └── stylesheets
│       └── main.css
└── test
    ├── ApplicationSpec.scala
    └── IntegrationSpec.scala

10 directories, 14 files

■ ソースを編集する
疎通確認としては必須ではないけど、一応 Hello Worldってことで、文言だけ変えておく。
package controllers

import play.api._
import play.api.mvc._

object Application extends Controller {

  def index = Action {
    Ok(views.html.index("Hello, world!"))
  }

}

■ Procfile を書く。

heroku 上で実行されるコマンドを記述するやつで、プロジェクトのルート直下に置いとくファイルだけど、これが Play のバージョンごとに違っていて、知らないとちょっと苦労する。2.2.x では、以下のような感じ。
web: target/universal/stage/bin/helloworld -Dhttp.port=${PORT}
"web:" の後が、"play run" だとか "target/start "になってると、古い方式なので、ちゃんと起動しない。あと、port の指定の後に、$JAVA_OPTS とか $PLAY_OPTSとか指定しているチュートリアルもあるけど、これも今のプロダクトだと動かなくなる。

■ git に入れる
$ git init
$ git add .
$ git commit -m "init"

■ heroku アプリを作る
$ heroku create
ここで、ブラウザから heroku にログインすると、Apps ページに今作ったアプリケーションが表示されているのが確認できる。

■ デプロイする
$ git push heroku master

■ 確認する
$ heroku ps
=== web (1X): `target/universal/stage/bin/helloworld -Dhttp.port=${PORT}`
web.1: up 2014/04/21 16:24:01 (~ 18s ago)
何か失敗してたら、up じゃなくて crashed とかになってる。

以下のコマンドで、ブラウザ上、Hello, world!って表示されるのを確認できる。
$ heroku open 

ログを見るには、heroku logs

Procfile が間違ってると、例えば、"web: play run"とか古い Playの設定の仕方だと、"bash: play: command not found"みたいな感じで、ログに吐かれている。

あと $JAVA_OPTS とか余計な指定が入ってると、"Bad application path: -Xmx384m"って書き出される。依存ライブラリの問題なんかもログに出力される。

2010年1月19日火曜日

andLinux/Scala lift

andLinux で Scala Lift を使う準備。

  • maven が入ってなかったら入れとく。Java とか無くても、つられてインストールされる。
    $ sudo apt-get install maven2

    バージョンを見ると Java も Maven もちょっと古いけど、まいっか。
    $ mvn --version
    Maven version: 2.0.9
    Java version: 1.6.0_0
    OS name: "linux" version: "2.6.22.18-co-0.7.4" arch: "i386" Family: "unix"
  • scala も入ってなかったら、入れる
    $ sudo apt-get install scala
    バージョン 2.7.3 が入った。
  • maven2 のアーキタイプでスケルトンをつくる (参考)
    $ mvn archetype:generate -U \
    > -DarchetypeGroupId=net.liftweb \
    > -DarchetypeArtifactId=lift-archetype-basic \
    > -DarchetypeVersion=1.0 \
    > -DremoteRepositories=http://Scala-tools.org/repo-releases \
    > -DgroupId=demo.helloworld \
    > -DartifactId=helloworld \
    > -Dversion=1.0
  • 実行する
    $ cd helloworld
    $ mvn jetty:run
  • ホストのブラウザで、http://192.168.11.150:8080/ を見てみる。なんかシュっとした感じのきれいな helloworld のトップ画面が表示される。

2009年12月7日月曜日

Scala / multiple classification

multiple classification (多重分類/多重型付け)について、Scala ベースで考えてみた。 Analysis Patterns」 から抜粋した上のクラス図(Odell表記)の意味は、
  • 顧客のインスタンスは、サブクラスである個人顧客または法人顧客のいずれかのインスタンスである。
  • 顧客のインスタンスは、サブクラスである重要顧客インスタンスである場合もあり、そうでない場合もある。
上図の分析モデルでは2つの系統の重なりを禁じていないので、ベン図で書くとこんな風に重なりが生じる。 こういうのを、オブジェクト指向分析の用語で multiple classification と言う。生活世界の感覚では、別段、不自然な事ではないけど、従来の single-classification ベースの言語で考えると、ちょっと面倒くさくなる。 つまり重なった部分についても、重要個人顧客、重要法人顧客といったように、組み合わせの分だけクラスを定義する事になる。これを避けて、種別を示す列挙定数を持たせるなどするけど、結局、分析と実装のギャップが広がってしまう。 で、前に Scala を少し調べたとき、分析と実装を乖離させる事なく、上述のような複数系統の型付けを自然に実装できそうな感じがしたので、ちょっと簡単なコードで確かめてみたい。 以下のようななんちゃって仕様を仮定する。
  • 顧客(Customer)は値引き率をあらわすメソッド discount() を持つ。
  • サブクラス 個人顧客(PersonalCustomer)の discount()は、2月生まれの人を2割引する
  • サブクラス 法人顧客(Corporation)の discount()は、従業員10人未満の法人を1割引する。
  • サブクラス 重要顧客の discount()は、個人/法人の discount() で値引きされた率に加えて、さらに3割引する。例えば 2月生まれの重要個人顧客は 2割引の 3割引で 4.4割引になる(1-(1-0.2)*(1-0.3)=0.44)。
こんなコードを書いてみた。
class Customer {
 def discount: Double = 0
}
class PersonalCustomer(val birthMonth: Int) extends Customer {
 override def discount: Double = if (2 == birthMonth) 2 else 0
}
class Corporation(val employeeNum: Int) extends Customer {
 override def discount: Double = if (10 > employeeNum) 1 else 0
}
trait PriorityCustomer extends Customer {
 override def discount: Double = (100 - (10 - super.discount) * 7) / 10
}
以下、結果。
scala> new PersonalCustomer(2).discount
res62: Double = 2.0

scala> (new PersonalCustomer(2) with PriorityCustomer).discount
res63: Double = 4.4

scala> new Corporation(5).discount
res64: Double = 1.0

scala> (new Corporation(5) with PriorityCustomer).discount
res65: Double = 3.7
とりあえず計算はできたらしい。重なり部分のクラスを個別に定義しなくても、型付けを多重化して仕様を合成できた。ただし、インタプリタでなくソースコードでの生成を考えた場合、組み合わせる型を with に続けてハードコードするのではなく、実行時に与えられるようにしたい。そうでないと、実質、個人重要顧客/法人重要顧客に相当する無名クラスを定義しただけにすぎない感じがする。これじゃ結局、single-classification だ。うーん、もうちょい調べないと・・・

2009年9月27日日曜日

Scala で待ち行列

しばらく Scala を触っていなかったので思い出す目的と、Actor を試してみる目的で、以前Erlang で書いた待ち行列のシミュレーションを Scalaで書き換えてみる事にした。
package exercise.actors

import scala.actors.Actor
import scala.actors.Actor._
import scala.collection.mutable.Queue;
import scala.actors.TIMEOUT  

case class Done(diff:Long)

object ExpRandom {
 def rand(m:Double):Double = {
      -m * Math.log(Math.random)
 }
}

class Service(observer: Observer) extends Actor {
  val μ = 0.5 //平均サービス率: 平均サービス時間2秒の逆数
  val queue = new Queue[Long]
  def serviceTime(): Double = {
    ExpRandom.rand(1.0 / μ)
  }
  val worker: Actor = new Actor {
    def act() {
      loop {
        receive {
       case 'serve =>
         Thread.sleep(Math.round(serviceTime() * 1000))
         sender ! 'afterProcessed
     }
      }
    }
  }
  worker.start
  def act() {
    var inService: Boolean = false
    loop {
      receive {
        case 'customerIn =>
          queue.enqueue(System.currentTimeMillis())
          Console.println("queue.length=" + queue.length)
          self! 'doService
        case 'afterProcessed =>
          inService = false
          queue.dequeue
          self! 'doService
        case 'doService =>
          if (!inService) inService = doService()
      }
    }
  }
  def doService(): Boolean = {
 if (queue.isEmpty) false
    else {
      val elapsed = System.currentTimeMillis() - queue.front
      observer! Done(elapsed)
      worker! 'serve
      true
 }
  }
}

class Customer(service: Service) extends Actor {
  val λ = 0.4 // 毎秒0.4個のリクエストが到着
  var interval: Double = 0

  def arrivalInterval(): Double = {
 ExpRandom.rand(1.0 / λ)
  }

  def act() {
    loop {
      receiveWithin(Math.round(interval * 1000L)) {
        case TIMEOUT =>
          service! 'customerIn
          interval = arrivalInterval()
      }
    }
  }
}

class Observer extends Actor {
  var requestCount:Int = 0
  var totalWaitingTime:Double = 0
  def act() {
    loop {
   receive {
     case Done(time)=>
          totalWaitingTime += time
          requestCount += 1
       Console.printf(
         "リクエスト%d; 待ち時間:%dms; 平均待ち時間=%dms;%n", 
         requestCount, 
         time, 
            Math.round(totalWaitingTime / requestCount))
       }
    }
  }
}

object QueueTheoryTest extends Application {
  val observer = new Observer
  val service = new Service(observer)
  val customer = new Customer(service)
  
  observer.start
  service.start
  customer.start
}
(シンボルを表すアポストロフィが文字列をくくるクオーテーションマークに認識されているらしく、Prettify が上手く動かない・・・) actor customerから平均到着率(λ)0.4個/秒でリクエストが届き、actor service が平均サービス率(μ)0.5個/秒でリクエストを処理する設定にしてみた。ρ=0.4/0.5、Ts=1/0.5 なので、平均待ち時間は Tw = ρ / (1 - ρ) * Ts = 8秒 = 8000ms となることが期待されるが、以下のような結果になった。
リクエスト1998; 待ち時間:19875ms; 平均待ち時間=8527ms;
queue.length=10
リクエスト1999; 待ち時間:25484ms; 平均待ち時間=8535ms;
リクエスト2000; 待ち時間:25047ms; 平均待ち時間=8544ms;
リクエスト2001; 待ち時間:25266ms; 平均待ち時間=8552ms;
queue.length=8
リクエスト2002; 待ち時間:22531ms; 平均待ち時間=8559ms;
なんか微妙。期待値8000msより結構大きい値になった。リクエスト2000個でもなかなか予想する値に落ち着かない。λ=2、μ=2.5にして回転率を上げてみるとリクエスト5000個で期待値1600ms に対して 1455ms と少し小さめの数値になった。なんだろう?もっと待てば予想する値に近づくのか、Java の乱数の分布やJava/Scalaのリアルタイム処理のクセなのか、あるいはコードに間違いがあるのか良くわからない。ただ今回は Actor の使い方がざっくりと解ればいいので、余り深く突っ込むのは止めておいた。

Erlang から Scala に移植するに当たって、erlang:send_after に当たるものが、どうも Scalaには見当たらなかったので、もう一つの Actor を使ってThread.sleepさせてメッセージ送信を遅延させるコードを書いた。「actor {}」ではなくて「new Actor」を使っているが、前者だと上手く動かず、調べておきたいところだが今回は時間切れ。あと react を使ってメッセージをやりとりしているときにThread.sleep()すると、他の Actorまで止まってしまうのだろうと予想していたが、この実験ではreceive でも react でもあまりはっきりした違いは見られなかった。後でちゃんと調べておこう。

まあ、せっかく勉強し始めた Scala の忘却防止と、少量の知識追加としては、今回はこんなもんだろう。あと Scala に移植しているうちに Erlang側コードに間違いやらまずいコードがあったのに気付いたので、これも後で直しておく。

2009年7月31日金曜日

Scala 練習5

今日も、ネットの連載記事を読みつついじってみる。
第4回 Scala言語を探検する(2)

今回は trait の説明が中心。「mix-in型の多重継承」、「アスペクト指向を簡易に実現」なんて書いてある。多重継承なんて何だかつまらない話っぽいけど、下の方に何気なくサラっと書かかれてる「インスタンス生成時にそのオブジェクトに対して個別にtraitの性質を与えることもできます。」ってところがとても面白い。多重型付けなんだな。

ちょっと古いけど Martin Fowler の「アナリシスパターンAnalysis Patterns)」(通称アナパタ)という絶品な良書があって、その付録の「A.13 汎化」で「多重継承(multiple inheritance)」と「多重型付け(multiple classification)」の違いについて説明がある。

要約すると「多重継承といっても個々のオブジェクトが持つのは単一の型だから、それは単一型付け(single classification)だけど、それに対して多重型付けは個々のオブジェクトが複数の型を持ちうるという別ものの話であり、概念的には後者の方が自然である。」といった内容。Scala の traitは多重継承だけでなく多重型付けもサポートしているようなので、分析レベルと実装レベルのギャップが狭くなるというわけで、これは歓迎。

ちなみにアナパタの同じ箇所に「多重型付け」と並んで「動的型付け(dynamic classification)」についても記述がある。この記事のサンプルをもじって言うと、Person with TPianoPlayer から例えば Person with TGuitarPlayer への、実行時の動的な変更が言語的にサポートされていれば「動的型付け」なんだけど、Scala ではどうなんだろう? いたるところで「Ruby は動的だが Scala は静的」なんて言われているようだからやっぱ無理かな。ちなみにJava や C++ は、もちろん動的型付けはサポートしていないから、 Strategy か State パターンを使うのが定石。

続くアスペクトのところもまあまあ面白いが、この記事では軽く紹介されているだけなので、フックのための小さい仕掛けのレベルを出ていない。もっとよく調べれば本格的な Separation of Concerns の技法の、Scala的なイディオムがあるのかもしれないが、Scala 初心者の自分にはまだ分からない。ちなみに mix-in といえば、昔アスペクト指向が流行始めた頃(2002年頃?)、AspectJ の対抗馬的な扱いで MixJuiceってのがあった気がするんだがどうなったんだろ?

最後の方に型の話があって、より純粋なオブジェクト指向言語って事らしいが、まあ、うん。

====
今日は、Eclipse をエディタとして使って、コンソールから :load して実行という変な使い方をしてみた。
scala> :load Person.scala
Loading Person.scala...
defined class Person
defined trait TTeacher
defined trait TPianoPlayer
defined class PianoplayingTeacher

scala> val t1 = new PianoplayingTeacher
t1: PianoplayingTeacher = PianoplayingTeacher@107e9a8
Eclipse のScala IDE でクラスを新規作成すると、[Finish]押してファイルも生成されてるのにフォームが閉じない。テンション下がる・・・

2009年7月30日木曜日

Scala 練習4

仕事ではほぼ常時テストファーストでやってるので、Scala で遊ぶのもユニットテストのフレームワークを使ってやってみたくなる。探してみると、Specs というのを見つけた。このサイトで QuickStart というのがあったのでやってみるが、せっかくなので Eclipse IDE を使う事にした。以下レポート。

◆試行環境
・Eclipse 3.5
・specs-1.5.0.jar
・JUnit 4.5 (Eclipse 3.5 のデフォルト)

◆新規 Scala プロジェクトを作る


◆Scala オブジェクトを生成する

QuickStartの step3 のとおりにコードを書くが、以下のようにビルドエラーとなる。

◆ライブラリを追加する
以下のように、SpecsのjarとJUnit4ライブラリを追加する。
そうするとエラーのマーカが消える。どうやらビルドできたらしい。

◆実行してみる
コンテキストメニューには「Scala アプリケーションとして実行」的なメニューは出ないので、[Run Configuration] から設定する。

QuickStartと同様のコンソール出力が得られた。

◆JUnitから起動
さて、これがやってみたかったのだが、どうしても Eclipse のテストランナーがテストクラスを認識しない。いろいろやってみたがギブアップ。まあ、そのうち分かってくるかもしれないから今は良しとしよう。

※Specs は良いが、Eclipse Scala IDEがいろいろ困ったちゃんで面倒くさい。言う事聞かない。まあ Scalaは人気あるみたいだし、そのうち何とかなるだろう。

※Specs のテストの書き方が全面的に EDSL で面白い。こういうのは実に良い。いいもん見つけたわ。

2009年7月29日水曜日

Scala 練習3

今日もちょっと Scala をいじってみた。

引き続きネットで連載してる記事を読みながらいろいろ試してみる。連載第三回だが筆者が変わったらしく、前二回のおさらいから始まり、いったん関数型っぽい話題は保留にしてJava 側から近接するアプローチで話が進む。

コード例も「Java と違ってこうも書ける」なんてのが多くて、今回はそれほど面白そうなのはない。引き継いで初回だからしかたないか。途中、パッケージスコープで関数を定義しているようなコードのスニペットがあるが、どうしてもエラーになる。記事中のコード例に何が欠けているのか調べようかとも思うんだが、なんだか一気にテンションが下がって、面倒くさくなってきた。

最後に Java とScalaの文法の違いが箇条書きされているが、まあわざわざ Java 以外の言語を JVM 上で動かそうと言うのだから、そういうのじゃなくて、もっと抜本的に違うところを読みたい。ただ記法レベルの話に混じって、static なメンバからシングルトンオブジェクトへの移行や、for ループなどの制御構造からオブジェクト構造への移行など、パラダイムのレベルでよりオブジェクト指向になっている項目もあって、これらはちょっと面白い。

今回は特に演習する事もないので、Eclipse に Scala IDEを組み込んでみた。1年以上前のちょっと古い記事だが、ITPro の「イマドキのIDE事情 29 EclipseでScalaプログラミング!」を参考にしてみた。

いろいろいじってみると、記事中に紹介されているTestPadがコンテキストメニューに出てこない。さらに、同じく紹介されているインタプリタの方は、一瞬コンソールに表示しようとする努力が見られるのだが、速攻で消えうせて何事もなかったかのようになってしまう。EclipseのバージョンはGalileo。さらにテンションが下がる。でもクヨクヨしない。

まあ、コード補完やその他の Eclipse の良い所はまあまあ使えるので、ある程度のコード量が増えてきたら便利になってくるだろうと思う。あとScala自体が普及すれば、それに応じて品質も上がって来るのだと思う。

ここまで書いて思い出したが、前回までの記事でも書かれていたような、あるいはScalaを語る他の言説でもよく言われているような「オブジェクト指向と関数型の融合」みたいな言い方はどうなんだろう。GoF本の昔から、既に Dylan とか CLOS とかあって、古臭いというと言いすぎだけど、かなりありきたりな話だと思うんだが。

ちなみに上記の「パッケージスコープで関数を定義する」コードは、Eclipse IDE上でも同様のエラーになった。何がまずいんだろうなあ。

2009年7月26日日曜日

Scala 練習2

先日に引き続き、ITProに載ってる Scala 講座の、「第2回 Scalaの基本的な文法」をやってみる。 今回のポイントは簡単な変数宣言から簡単な関数の定義まで。ハイライトは高階関数とパターンマッチングといかにも関数型言語風の再帰処理。いろいろ疑問も生じるが、そのうちわかるのだろうと、とりあえず最後までやってみる。掲載されているコードは、特に問題なく動作した。 復習のために、今回で紹介されていた事と、検索でたどり着いた他のサイトから得た少々の知識を使って自主練習してみた。以下、分散を求めるコードを、まず再帰とパターンマッチングを使って書く。次に、これを高階関数を用いるパターンに書き直してみる。更に現時点の手持ちの知識で書き直して、いろいろ試してみる。 1.高階関数なし まず単純にList中の要素の合計を求める sum() 関数を定義してみる
def sum(l:List[Double]):Double = l match {
 case Nil => 0
 case head::tail => sum(tail) + head
}
各要素について平均値との差の2乗を計算しリストを生成する varianceSub() 関数を以下のように書く(これは後で直す)
def varianceSub(list:List[Double], average:Double):List[Double] = list match {
 case Nil => Nil
 case head::tail => Math.pow((head - average), 2) :: varianceSub(tail, average)
}
最後に、varianceSub() で求めたリストの平均値を算出して分散を求める variance ()関数を書く
def variance(list:List[Double]) = {
 average(varianceSub(list, average(list)))
}
2.高階関数を使ったやつ 上記コードを無名の高階関数を使って書き直してみた。
def sum2(l:List[Double])(f:Double=>Double):Double = l match {
 case Nil => 0
 case head::tail => sum2(tail)(f) + f(head)
}

def variance2(list:List[Double]) = {
 val avg = sum2(list)(elem => elem) / list.length
 sum2(list)(elem => Math.pow(elem - avg, 2)) / list.length
}
高階関数ありと無しの二つのコードで、以下のように同じ結果が得られる事が確認できた。
scala> variance(List(46.1, 52, 48.5, 46.5, 46, 44.8, 48.5, 53.4))
res60: Double = 8.169375
scala> variance2(List(46.1, 52, 48.5, 46.5, 46, 44.8, 48.5, 53.4))
res61: Double = 8.169375
3.標準のList操作関数を使ってみる sum2 のようなのはList操作関数としておそらく標準的に提供されているに違いないと考え、検索してみると、やはりある。そこで variance2() 関数を sum2()を使わないパターンで書き換えてみた。
def variance3(list:List[Double]) = {
 val avg = list.foldLeft[Double](0) {(sum, y) => sum + y} / list.length
 list.foldLeft[Double](0) {(sum, elem) => sum + Math.pow(elem - avg, 2)} / list.length
}
4.タプルを使ってみる ここまでのコードは、平均値を算出するときと、差の2乗値 を算出するときの2回往復分リストを走査しているが、本来は、行きで平均値を算出し戻りで差の2乗を出せるはずなので無駄がある。但しこれをやるには、差の2乗と一緒に平均値も同時に関数の戻りとして返す仕組みが必要になる。そこで調べてみると、タプルというものを使って複数の戻り値を返せるというので使ってみた。 まずリストの「先頭→末尾」方向に走査する際に合計を出しておき、末尾(case Nil のところ)で平均値を算出し、これを Tuple の要素1に入れておく。折り返して「末尾→先頭」方向に戻るときに、この平均値を使って差の2乗を算出し、これを足しこんで要素2に保持しておく。
def variance4(l:List[Double], sum:Double, len:Double):Tuple2[Double,Double] = l match {
 case Nil => new Tuple2[Double, Double](sum / len, 0)
 case head::tail => x(tail, sum + head, len) match {
  case Tuple2(avg, sum) => new Tuple2(avg, sum + Math.pow(head - avg, 2))
 }
}
最終的に関数が終了したときに返されるのは、差の合計を保持する Tupleオブジェクトなので、ここから分散を得るにはTupleの要素2を取り出して、要素数で割る必要がある。適当に関数を定義してもよいが自明なので省略。下のような結果が得られ、正しく動作している事が確認できた。
scala> aList = List(46.1, 52, 48.5, 46.5, 46, 44.8, 48.5, 53.4)
aList: List[Double] = List(46.1, 52.0, 48.5, 46.5, 46.0, 44.8, 48.5, 53.4)

scala> variance4(aList, 0, aList.length)._2 / aList.length
res103: Double = 8.169374999999999
上記コードのように戻り値を二つ返す際にTupleを使う場合に、毎回 new しているのが気になる。Tuple がイミュータブルにできているようなので、このように書かざるを得なかったが、今後マシなやり方を探してみたい。

2009年7月24日金曜日

Scala 練習1

ITPro で Scala 講座の連載を見つけたので、「第一回 なぜScalaなのか?」の通りにやってみる。

・指定サイトからのファイル入手からインストールまで、すんなり成功。ただ、動作確認のためバージョンを見る箇所で、記事中、2.7.1.finalとあるが 2.7.5.finalと表示された。バージョンが上がっているらしい。

・対話形式での実行 → 成功。試しにわざとfooと入力してみると、以下のようなエラーメッセージ

scala> foo
<console>:5: error: not found: value foo
foo
^


・スクリプトとして実行 → 成功。ここでも正しい記述の代わりに"foo"と書いて実行してみると、以下のエラー出力を得た。
(fragment of helloWorld.scala):1: error: not found: value foo
foo
^
one error found
!!!
discarding <script preamble>


・コンパイルして実行 → 成功。javaのクラスコードが二つ生成されるが、それぞれの役割は何だろう。特に"$"がついてるやつは?

・Carクラスを作るところで、記事の通りに「>scalac CarMain.scala」と打ちこむと、
CarMain.scala:3: error: not found: type Car
と言ってくる。「>scalac *.scala」としてみると成功。

・記事中に Car.scala を UTF-8で保存するよう指示があるが、試しにSJIS で保存して再試行してみると、デコード不能という事でコンパイル時に Java の IOException が発生。そこで「>scalac -encoding "sjis" Car.scala」として、encodingを指定してみると、SJISでも試行成功。

・Javaコードから使用するところの試行では、classpathに含めている scala-decoder.jar なるものが見当たらずコンパイル失敗。代わりに scala-library.jarを指定すると成功。scalaのバージョンの違いだろう。

・ついでに、カレント直下のディレクトリ src と classesを、それぞれソースディレクトリと出力ディレクトリに指定してみる。以下のようにやってみたらできた。
scalac -d classes -sourcepath src src\*.scala

----
連載目次はここ