名前(引数)
の形で適用できるものを指す。
途中で出てくるコードは Scala 2.11.4 の REPL で確認した。 間違いを見つけたら教えてほしい。
広義の関数には次の2つに大別される。
apply()
メソッドをもつオブジェクトメソッドは def
を使って定義される。
例えばこんな感じ。
scala> def f(i: Int): Int = i + 1
scala> f(10)
res0: Int = 11
一方で apply()
メソッドをもつオブジェクトはこんな感じ。
scala> object f { def apply(i: Int): Int = i + 1 }
scala> f(10)
res1: Int = 11
apply()
メソッドをもてば良いので class
のインスタンスでも良い。
scala> class C { def apply(i: Int): Int = i + 1 }
scala> val f = new C
scala> f(10)
res3: Int = 11
メソッドと apply()
メソッドをもつオブジェクトで実用上の違いはあるだろうか?
関数型言語の性質として「関数が第一級である」ことがあげられる。 次の特徴を持つものを第一級と呼ぶ(関数プログラミング実践入門より引用)。
- リテラルがある
- 実行時に生成できる
- 変数に入れて使える
- 手続きや関数に引数として与えることができる
- 手続きや関数の結果として返すことができる。
広義の関数である2種類について、それぞれみてみよう。
メソッドについて考えてみると、上記のいずれも満たさない。 よって第一級ではない。
apply()
メソッドをもつオブジェクトはどうだろう?
リテラルがない。よって第一級ではない。
しかし、それ以外の特徴は満たしている。
実行時に生成できる、変数に入れて使える、手続きや関数の引数や結果になれることがメソッドとの違いだ。
apply()
をもつオブジェクトのうち、 Function1, Function2 , … Function22 trait を継承するオブジェクト(以下 FunctionN オブジェクトと呼ぶ)は言語から特別な扱いを受ける。
FunctionN オブジェクトにはリテラルがある。
Function1
のドキュメントに書いてあるように (x: Int) => x + 1
と書けば、それは
new Function1[Int, Int] {
def apply(x: Int): Int = x + 1
}
と同じ意味になる。
FunctionN オブジェクトは apply()
をもつオブジェクトなので、リテラルが存在することにより、晴れて第一級であるといえる。
逆に言えば、広義の関数の中で FunctionN オブジェクトのみが第一級と呼べる。
というとき、ここでいう「関数」は FunctionN のみを指すことになる(以下、狭義の関数と呼ぶ)。
SLS 8.5 の Pattern Matching Anonymous Functions には { case i => i + i }
と(パターンマッチみたいに case がいくつあってもいい)いう記法も FunctionN オブジェクトになりうる。
「なる」ではなく「なりうる」なのは、型的に FunctionN が要求される箇所においては FunctionN として解釈される一方で、PartialFunction が要求される箇所においては PartialFunction として解釈されるからだ。
scala> { case i => i + 1 }
<console>:9: error: missing parameter type for expanded function
The argument types of an anonymous function must be fully known. (SLS 8.5)
Expected type was: ?
{ case i => i + 1 }
^
scala> { case i => i + 1 }: Function1[Int,Int]
res12: Int => Int = <function1>
書ける箇所が限定されるが、これもリテラルと呼んでいいのだろうか、、少し自信がない。
メソッド名の後ろに _
を置くことで、メソッドから FunctionN へ変換できる。
scala> def f(i: Int) = i + 1
f: (i: Int)Int
scala> f _
res0: Int => Int = <function1>
f _
は x => f(x)
と同じ意味であり、
new Function1[Int, Int] {
def apply(x: Int): Int = f(x)
}
と書くのと同じである。 この変換を eta expansion と呼ぶ。
また、FunctionN が型として要求される箇所にメソッドを書いても同じように eta expansion される。
なお、FunctionN からメソッドへは変換できない。
メソッド、apply()をもつオブジェクト、FunctionN の関係を図示するとこんな感じになる。
FunctionN オブジェクトは、それ以外の広義の関数と比べて違いがあるだろうか?
FunctionN オブジェクトは可変個引数を取ることができない (実は今日プログラミング中に可変個引数とれないと気付き、 チャットで昔はできるバグがあった と教えてもらったことがきっかけでこれを書いてます)。
Function1 において apply()
は次のように宣言されている。
def apply(v1: T1): R
T1
, R
は型パラメータである。
メソッドで可変個引数を宣言するときには 型*
という記法を使う。
型*
は型ではないので、それを T1
として使うことはできない。
Function2~22においても同じ事情である。
可変個引数を取るメソッドから変換すると、Seq
に変換される。
scala> def f(i: Int*) = i.sum
f: (i: Int*)Int
scala> f _
res0: Seq[Int] => Int = <function1>
リテラルではデフォルト引数を指定できない。 またメソッドから変換する場合にはデフォルト引数は受け継がれない。
自分で FunctionN の subclass を作ればできるが、実際やる人はいないよね。
scala> object f extends Function1[Int, Int] { def apply(x: Int = 2) = x + 1 }
scala> f()
res11: Int = 3
というわけで、実質デフォルト引数は使えない。
リテラルでは型パラメータを指定できない。
メソッドから変換する場合には、型パラメータ部分が Any
になるようだ。
scala> def f[A](i: A) = println(i)
f: [A](i: A)Unit
scala> f[Int](1)
1
scala> val g = f _
g: Any => Unit = <function1>
scala> g[Int](1)
<console>:10: error: value g of type Any => Unit does not take type parameters.
g[Int](1)
というわけで、型パラメータは使えない。
リテラルでは implicit 引数を書けない。
メソッドから変換する際は、変換の際に implicit が解決される。
scala> def f(i: Int)(implicit j: Int) = i + j
f: (i: Int)(implicit j: Int)Int
scala> f _
<console>:9: error: could not find implicit value for parameter j: Int
f _
^
scala> implicit val implicitInt: Int = 10
implicitInt: Int = 10
scala> f _
res1: Int => Int = <function1>
implicit 引数は使えない。
メソッドでは問題にならないが、FunctionN は Function22 までしかないので、23個以上の引数を取り扱えない。
scala> (a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int) => a + 1
<console>:9: error: implementation restricts functions to 22 parameters
(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int) => a + 1
^
メソッドから変換しようとしてもエラー。
scala> def f(a: Int, b: Int, c: Int, d: Int, e: Int, f: Int, g: Int, h: Int, i: Int, j: Int, k: Int, l: Int, m: Int, n: Int, o: Int, p: Int, q: Int, r: Int, s: Int, t: Int, u: Int, v: Int, w: Int) = 1
scala> f _
<console>:10: error: implementation restricts functions to 22 parameters
f _
^
23個以上の引数は使えない。
メソッドからの変換はうまくいく。
scala> def f(i: => Int) = i + 1
f: (i: => Int)Int
scala> f _
res16: (=> Int) => Int = <function1>
リテラルでは (i: => Int) => i + 1
などと書きたくなるが、これは通らない。
scala> (i: => Int) => i + 1
<console>:1: error: identifier expected but '=>' found.
(i: => Int) => i + 1
^
StackOverflowによれば、次のようにすれば良いようだ。
val f: (=> Int) => Int = i => i + 1
たしかにこれは通る。
(i => i + 1): ((=> Int) => Int)
変数に入れたくない場合は、型注釈を後に付けても良いようだ。
Call by name 引数については、面倒だが使える。
広義の関数は、メソッドと apply()
をもつオブジェクトの2つに分けられる。
apply()
をもつオブジェクトの中に FunctionN オブジェクトがあり、第一級なのは FunctionN オブジェクトのみ。
FunctionN オブジェクトは、その定義や利用法により出来ないことがいくつかある。
メソッドは第一級じゃないけどいろいろ便利だな。
]]>AWS の CloudFormation を使おうと思ったら JSON を書かなければならない。 サンプルで提供されているJSONのひとつ を見てみて、自分にはとても書く気にはなれなかった。 理由は2つ。
1つめはコメントが書けないこと。
JSON として Description
が書けるようになっているが、自由な行にコメントを書きたい。
コメントがないと後日見た時に意図が伝わらないことがあり、メンテナンスが困難になる。
2つめはJSONであるがゆえにDRYに書けないこと。 たとえばサンプル中に出てくる次の部分をみてみよう。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 |
|
げぇー。ほとんど同じ内容が書かれている。 違う部分を探すのが難しい。 プログラマがこれを見たら共通部をくくりだしたくなるのは自然だし、くくりだせないということがわかったら発狂するか無気力になるだろう。 大きな例になればなるほど、このような繰り返しが多数出現する。つらい。
解決策のひとつは DSL を書いて JSON を生成する方法だ。 cloudformation-ruby-dsl や troposphere や他にもいろいろ探せば出てくる。 DSLが問題を解決するか否かは、、まあDSL次第だ。 内部DSLの場合にはベースとなるプログラミング言語そのものの機能で問題を解消できる。 ざっと見たところ内部DSLを採用している例ばかりだった。
しかし、DSLを使うことで新たな問題が発生する。 CloudFormation には日々新たな機能が追加されており、DSLが機能をサポートするまでに時間がかかるのだ。 機能追加の速度が遅い場合にはこれは問題にはならないが、AWSはすごい速度で機能を追加してくる。 使いたいものが使いたいときに使えないようでは残念な気持ちになることが想像できる。
もちろんDSLに自分でpatchを当てればいいのだが、特定のDSLにそこまで思い入れがあるわけではない。 自分が求めているのは JSON を便利に書く方法であって、DSLではないということに気づいた。
私は普段 Play framework を使って開発をしている。 Play で設定を書くときには HOCON というフォーマットを使う。 HOCON は JSON の superset であり、人間が読み書きするのに最適化されている(Human Optimized) 。 JSONを便利に書きたいのだから、HOCONこそ自分が求めるものだ(とその時は思った)。
HOCON を JSON に変換する方法が欲しかったので、コマンドラインツール hocon2json を作った。
問題は解決された(やった)。
敢えてこの方法の問題を挙げるとしたら、同僚に HOCON の書き方を教えなければならない点、コマンドラインツールをインストールしなければならない点だ。
HOCONで問題は解決したものの、やっぱ HOCON マイナーだし、説明するのちょっとつらいな、という思いがしばらくくすぶっていた。 あるとき、JSONてそもそも JavaScript Object Notation の略だし、JavaScript で書けばいいんじゃないか、と思い立った。 JavaScript であれば知名度抜群でだれでも知っているし、コメントも簡単にかけるし、共通化も簡単だ。
実現方法は、JavaScript 内で JSON として使う Object を作って、 console.log(JSON.stringify(obj))
とするだけだ。
Pretty print したけば console.log(JSON.stringify(obj, null, 2))
とすれば良い。
今どきどこにいっても Node.js は入っている気がするし、もし無いときにも手元には必ずブラウザがあるので、ツールが無くて変換できないということは無いだろう。
CoffeeScript の実行環境も普及していると仮定すれば JavaScript の代わりに CoffeeScript で書いてもいいだろう。 その辺は普及の仮定と好みによる。
CloudFormation の JSON を書くには JavaScript/CoffeeScript が自分的ベスト(今のところ)。
]]>ドメインモデルとは?ドメインエキスパートの知識を厳密に構成、厳選して抽象化したものだ。
モデルと設計の核心が相互に形成しあう。
モデルと実装が剥離しては効果が薄い。実装とモデルを緊密に保つべしとのこと。 モデルの形状として何を想定しているのだろう?プログラムコードかな? プログラムコードをドメインエキスパートに読ませるのはしんどいのではないだろうか。 かといって記述用の擬似言語を設計するのもつらい。
モデルは、チームメンバ全員が使用する言語の基板である。
ドメインエキスパートも開発者もモデルの言葉を使ってコミュニケーションしよう、という話。 同じ言葉を使うことで不要な摩擦を除くことができる。
わかりやすい話ではあるが、日本においては日本語と英語の選択をしなければならない。 モデル言語として英語を使う場合、大半の日本人について、英語ネイティブスピーカーと同程度に英単語を自然に扱う能力は無いことは明白だ。 議論を重ねる上でモデル言語は洗練されていくが、その前提には高い言語能力が仮定されている。 英語を選択した場合、洗練がうまくいかない可能性があるのではないか。 一方で日本語を選択した場合、開発者が同じ単語を使うためには日本語でコードを書くことになるのではないか。 日本語でのプログラミングはプラットフォームによっては許容されるかもしれないが、一般に避けたいものである。
「モデルの構築と表現言語の選択」というのは日本人特有の課題だなあ。
ドメインエキスパートと議論を重ねることで知識を噛み砕きモデルを構築する話。 ドメインエキスパートと開発者が共同作業することが重要。 ほらほら、新しい言語とかフレームワークとかばっかりみてないで、対象業務がどうなっているか興味を持とうよ、てなことが書いてある。
だれも最初から完璧人間ではないのでモデルの構築は発見的に行われる。 変化を許容する必要がある。
コミュニケーション方法に言及するあたりが本書のスタンスをよく表している。
今日はここまで
]]>というわけで Pythonによるデータ分析入門 の献本を頂きました。ありがとうございます。 大学時代の友人たちが翻訳しており、発売を楽しみにしていました。
早速レビューします。
Wes McKinney さん(@wesmckinn)は、AQRと いう投資運用会社でクオンツとして働いた経歴があり、その時に自分が欲しい ものを作り始めたのが pandas でした。今年、 DataPad というデータ可視化のサービスを行う会社 を San Francisco で立ち上げて CEO としてご活躍中です。
Blog では PyCon での pandas 関連の発表 資料を見ることができます。
この本で紹介されている分析方法では、Pythonの各ライブラリ・ツールが以下 の役割を担っています。
各章でそれぞれについて、コード例を元に使い方が紹介されています。 コードは IPython からの入力形式になっており、対話的な利用を想定しているようです。 分析をする際には探索的にやる(何かの結果を得て、次の疑問が湧く、以下ループ) ことが多いので、IPython から対話的に使うのは実際的だと思います。
オープンソースなデータ分析といえば R だと思いますが、 R と比べて Python を使うメリットは、汎用言語であることでしょう。
Rは統計分析処理に特化しており、統計分析関連のことをするには向いています。 その一方で、統計分析の前処理や後処理、その他雑多なことを R でやるのは、不可 能ではありませんがしんどいものがあります。 Rでやるとしんどい部分は、通常、汎用言語やExcelなど他のツールでカバーします。 私が以前翻訳させていただいた RとRubyによるデータ解析入門 では、タイトルにもあるようにRubyをRと合わせて使う方法を採用しています。
本書で紹介されているツールセットは全て Python 製です。ひとつだけを覚えれば、 他のものを覚えなくて良いというのは大きなメリットです。分析者が欲 しいのはツールではなく分析結果ですから、学習コストは低ければ低いほど良 いのです。 私もそうですが、普段、汎用言語(Java、Ruby、Python、Cなど)を書いている プログラマにとって、Rの学習コストは比較的高いと思います。それはRが言語 として悪いということではなく、汎用言語と違いが大きいからだと思います。
システムのコンポーネントとして利用する場合にも Python であるということ はメリットになります。 Rの場合には Shiny Server などで APIを公開し、HTTP経由で利用する方法が広まりつつありますが、プロセス数の制限など から本気で使うには有償の契約が必要な状況です。 pandas の場合はただの Python なので Django と組み合せて web application を構成したり、他の Python 製フレームワークとの組み合せも自 由自在でしょう。
魅力的に思える pandas ですが、Rの豊富なライブラリ群を どこまで置き換え ることができるのか、私にはわかりません。 あらかじめ分析したい内容が決まっている場合は、pandasと周辺ライブラリで 実現できそうか確認してから取りかかったほうが良いでしょう。
教えてくれること
教えてくれないこと
この本の優れている点は Python におけるデータ分析プラットフォームを1冊で ひと通り説明しているところでしょう。
以下に該当している人は読むといいと思います。
私もまだ手を動かしながらは読んでないので、これから時間をみてじっくり読みたいと思います。 この手の本は手を動かさないと半分も身につかないですからね。
]]>モナドであるためにはモナド則を満たさねばならない。 モナド則は以下の3つ。
(m flatMap f) flatMap g == m flatMap (x => f(x) flatMap g)
unit(x) flatMap f == f(x)
m flatMap unit == m
ちなみにすごいHaskell本でモナド則は次のように書かれている。
(m >>= f) >>= g
と m >>= (\x -> f x >>= g)
が等価return x >>= f
と f x
が等価m >>= return
と m
が等価flatMap
を >>=
に、 unit
を return
に読みかえれば同じ。
同じモナド則の説明なので当たり前といえば、当たり前だけど。
覚えるのが大変だから名前は揃えて欲しかったなあ、とは思う。
>>=
は bind と読むらしいので、 flatMap は bind、 return は unit。
で、Scala の Try がモナドじゃないというのは、Left unit (左恒等性) を満たさないから。
任意の f
と expr
において、Try(expr) flatMap f
が f(expr)
と等しければ、
left unit が満たされていると言える。
しかし、f
または expr
が例外を投げた時にこれらが等しくならない。
Try(expr) flatMap f
では(fatal 以外の)例外が発生することはなく Failure
型の
戻り値になるのに対し、f(expr)
は例外が発生する。
よって Try はモナドではない。
ああ、残念。Try はモナドでは無かったか。
でも、 left unit を満たしてなくても for
式では便利に使えるからいいんだよ。
と Odersky 先生はおっしゃっておられた。
正直なところ、モナド則を満たしているとどれだけ嬉しいのかよくわかってない。 for comprehension で便利に使えればそれでいいじゃん、くらいの低い認識しかない。 実際このコースの3週目の講義を聞いているところだが、「Try は正確にはモナドじゃないけど、 モナドみたいなもんだから、これ以降モナドって呼ぶね」ということになっている。
]]>間違っているところがあったらコメントで教えてもらえると大変嬉しいです。
A: app/assets は CoffeeScript, LESS など preprocess するもの。public はしないもの。
A: Action の先頭に implicit request =>
を付ける。
Action { implicit request => ... }
Action.apply() がオーバーロードされてて、引数が call by name のもの関数のものがある。
def apply(block: => Result)
def apply(block: Request => Result)
implicit request =>
を付けたときは引数が関数の方。
request は implicit parameter。
A: scala.annotation.implicitNotFound
で annotation されているから。
A: Form#fold
を使う
A: @import some.package.MyClass
view の先頭(かつ、パラメータの後!)でしか import 出来ないので注意。
A: どうやらそういうものらしい。パッケージ名フル修飾で書く。
A: Symbol -> Any のタプルをずらずらと渡す。
@helper.inputText(userForm("name"), 'id -> "name", 'size -> 30)
A: ‘_label -> “ほげ” を追加する。
@helper.inputText(userForm("name"), '_label -> "ほげ")
_label
以外の特別なキーはここ。
http://www.playframework.com/documentation/2.2.x/ScalaCustomFieldConstructors
A:
@helper.form(...) {
...
<button type="submit"></button> // ←ここに直接書く。
}
<dd class="info">Required</dd>
←これA: '_showConstraints -> false
を @helper.inputFoo()
に渡す。
A: war にできないぽい。 http://stackoverflow.com/questions/14985783/deploy-play-as-a-war-file-into-a-servlet-container-even-if-it-uses-jpa-heavily
A: play dist で zip にして展開するのが良さそう
A: routes.MyController.action(arg)
http://www.playframework.com/documentation/2.2.x/ScalaRouting
A: play.api.Logger.info()
など。
A: SecureSocial か play2-auth
A: SES の smtp interface で送れる。
A: securesocial.core.providers.utils.RoutesHelper.login()
とか
A: http://securesocial.ws/guide/views-customization.html の手順でできそう。
統合する方法は用意されているか? A: 用意されていない。
A: できない!
http://stackoverflow.com/questions/10069217/rolling-an-evolution-back
A: evolutions の 1.sql を吐き出してくれる機能はある。
A: import scala.slick.driver.MySQLDriver.simple._
したら通った。どういうこと..
A: https://github.com/tototoshi/slick-joda-mapper
A: < でいいっぽい。
import com.github.tototoshi.slick.JodaSupport._
が必要。
com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException: You have an error in your SQL syntax; check the manual that corresponds to your MySQL server version for the right syntax to use near ':26:48.548+09:00' at line 1
A: slick-joda-mapper が古かった(0.1.0だった)。0.4.0 にあげたら解消した。
A: http://stackoverflow.com/questions/14840010/how-do-you-print-the-select-statements-for-the-following-slick-queries によるとできない。
A: Query()
でくるむといい。
A: http://d.hatena.ne.jp/tototoshi/20130329/1364484806
FakeApplication(
additionalConfiguration =
inMemoryDatabase(name = "default", options = Map("DB_CLOSE_DELAY" -> "-1"))
)
new WithBrowser(app = dbApp)
(dbApp は DB_CLOSE_DELAY 指定した FakeApplication)でエラーになるA: 第一引き数 webDriver も指定したらエラーでなくなった。(初期化順の問題?)
new WithBrowser(webDriver = Helpers.HTMLUNIT, app = dbApp)
A: -Dconfig.file
で設定ファイルを切り替える
A: -Ddb.default.migration.auto=true
最初まちがって -Ddb.default.migration.initOnMigrate=true
を指定して悩んでた。
A: GzipFilter でできるっぽい。
http://www.playframework.com/documentation/2.2.x/GzipEncoding
appDependencies に filters を追加する必要あり。
A: character_set_client, character_set_server が utf8mb4 になっていることを 確認する。
サーバとクライアントのネゴシエーションで決まる。 Slick での character_set_client, character_set_server の確認方法。
import scala.slick.session.Database
import Database.threadLocalSession
import scala.slick.jdbc.{GetResult, StaticQuery => Q}
val jdbcUrl = "jdbc:mysql://localhost:3306/my_db?user=myUser&password=myPassword"
Database.forURL(jdbcUrl, driver = "com.mysql.jdbc.Driver") withSession {
val q = "SHOW VARIABLES LIKE 'char%'"
Q.queryNA[(String, String)](q).foreach(println)
}
サーバの設定で character_set_server を utf8mb4 に設定し、 JDBC には characterEncoding=utf8mb4 をつけないのが正解ぽい。 JDBC URL に characterEncoding=utf8mb4 をつけると、例外がおきるので注意。
A: こんな感じ。
import play.api.db.slick.DB
import scala.slick.driver.MySQLDriver
if (DB("default").driver == MySQLDriver) {...}
A: /*! */
でくくると良い。
http://stackoverflow.com/questions/15885814/use-mysql-in-dev-prod-and-h2-in-test
/*! CHARACTER SET utf8mb4 */;
をつけておくと MySQL でだけ処理され、H2では処理されない。
Sample としてついてくる zentask や、 「play scala ユーザ登録」でググって上の方に出てくる ペ ー ジ を見ると、いずれもパスワードを生で保存している。
パスワードは生で保存していると何かの拍子に痛い目に合うので、真似したくない。 きっと Rails の devise みたいに再利用できるコンポーネントがあるだろうから、 それを使いたい。
Google+ のコミュニティで訪ねてみたところSecureSocial と play2-auth を教えてもらった。 SecureSocial を試してみる。
Play は 2.2.1, Scala でやる。
プロジェクト作成。アプリ名はログインしたいだけだから just-login
にする。
$ play new just-login
http://securesocial.ws/guide/installation.html に従って進めていく。
project/Build.scala を追加。
import sbt._
import Keys._
object ApplicationBuild extends Build {
val appName = "just-login"
val appVersion = "1.0-SNAPSHOT"
val appDependencies = Seq(
"securesocial" %% "securesocial" % "2.1.2"
)
val main = play.Project(appName, appVersion, appDependencies).settings(
resolvers += Resolver.url("sbt-plugin-releases", new URL("http://repo.scala-sbt.org/scalasbt/sbt-plugin-releases/"))(Resolver.ivyStylePatterns)
)
}
重複した情報になるので、 build.sbt からは name
と version
を削除した。
conf/route にルーティング追加。それっぽいエンドポイントが一式備わってて、それっぽい感じ。
# Login page
GET /login securesocial.controllers.LoginPage.login
GET /logout securesocial.controllers.LoginPage.logout
# User Registration and password handling
GET /signup securesocial.controllers.Registration.startSignUp
POST /signup securesocial.controllers.Registration.handleStartSignUp
GET /signup/:token securesocial.controllers.Registration.signUp(token)
POST /signup/:token securesocial.controllers.Registration.handleSignUp(token)
GET /reset securesocial.controllers.Registration.startResetPassword
POST /reset securesocial.controllers.Registration.handleStartResetPassword
GET /reset/:token securesocial.controllers.Registration.resetPassword(token)
POST /reset/:token securesocial.controllers.Registration.handleResetPassword(token)
GET /password securesocial.controllers.PasswordChange.page
POST /password securesocial.controllers.PasswordChange.handlePasswordChange
# Providers entry points
GET /authenticate/:provider securesocial.controllers.ProviderController.authenticate(provider)
POST /authenticate/:provider securesocial.controllers.ProviderController.authenticateByPost(provider)
GET /not-authorized securesocial.controllers.ProviderController.notAuthorized
次に conf/play.plugins
を作成し、以下の内容を記述。
ユーザ名とパスワードでログインしたいだけなので、
Twitter やら Facebook やらでログインするためのプラグインはざっくり削る。
1500:com.typesafe.plugin.CommonsMailerPlugin
9994:securesocial.core.DefaultAuthenticatorStore
9995:securesocial.core.DefaultIdGenerator
9996:securesocial.core.providers.utils.DefaultPasswordValidator
9997:securesocial.controllers.DefaultTemplatesPlugin
9998:service.UserServiceImpl
9999:securesocial.core.providers.utils.BCryptPasswordHasher
10004:securesocial.core.providers.UsernamePasswordProvider
9998:service.UserServiceImpl
の行に書いた service.UserServiceImpl
は
自分の環境に合わせて実装する必要がある。
InMemoryUserService.scala
の内容をコピってきて、パッケージ名を service
に、クラス名を UserServiceImpl
に
変えて、 app/service/UserServiceImpl.scala
として保存する。
http://securesocial.ws/guide/configuration.html にのっとって
conf/application.conf
に smtp
の設定と、 include "securesocial.conf"
を書く。
conf/securesocial.conf
は Sample configuration
の内容を書く。
assetsController=controllers.ReverseMyCustomAssetsController
は
Asset のコントローラを自前で作っているときだけ必要ぽいので、コメントアウトする。
MailerPlugin のソース
を眺めるとわかるように smtp.mock = true
を設定しておけば実際のメールは送信されず、
コンソールにメールの内容が出力される。
smtp {
... 他の設定
mock = true
}
ここまでで、ユーザ登録とログイン、ログアウト、パスワード忘れが実現できた。
パスワードが生で保存されることはなく、BCryptPasswordHasher
によって生成された hash が格納される。
UserService はメモリ上じゃなく、DBへ格納するように実装することになる。 その際は https://github.com/jaliss/securesocial/pull/163/files のコードが参考になりそうだった。
getOrElse()
は次のような定義にしていた。
public static function getOrElse<A>(opt: Option<A>, els: A) {
return switch (opt) {
case Some(v): v;
case None: els;
}
}
scala.Option の getOrElse()
ぽく使いたかったのだが、実際に使ってみるとなんか違う。
opt.getOrElse(sideEffectFunction());
上で sideEffectFunction()
は何らかの副作用をもつ関数だ。
Scala の場合、 opt
に値が入っていた場合には sideEffectFunction()
は評価されない。
副作用も発生しない。
上で定義した Haxe 用 getOrElse()
では opt
の内容によらず sideEffectFunction()
が評価され、副作用が発生する。
関数を呼び出すときには、まず引数を評価してから関数の実行に移るので、当たり前といえば当たり前だった。
Scala には call-by-name が言語上用意されているおり、
scala.Option の getOrElse()
はこれを利用している。
型を確認すると final def getOrElse[B >: A](default: ⇒ B): B
となっている。
⇒ B
が引数の型だが ⇒
を付けることにより、関数呼び出し前には評価されず、関数内で default
を参照したところで評価されるようになっている(call-by-name)。
なお [B >: A]
は型の境界で、B は A の supertype であることを表している。
default
に何かを入れると A
と default
の型との共通 supertyper を探して B
を推論してくれる。
いいなあ。Scala。
さて Haxe には call-by-name が無い。 JavaScript も call-by-name が無いが、彼らは callback でそれを代用する。 Haxe でも同じようにできる。
public static function getOrElse<A>(opt: Option<A>, els: Void -> A) {
return switch (opt) {
case Some(v): v;
case None: els();
}
}
els
部を関数渡すようにしておけば、実際に opt
が None
のときだけ els
を評価することができる。
このとき呼び出し元は以下のようになる。
opt.getOrElse(function() { return sideEffectFunction(); });
// function が return ひとつだけのときは以下のように省略できるが、まだ長い。
opt.getOrElse(function() return sideEffectFunction());
Haxe では関数リテラルを短く書く方法がなく、return を省略できないため長ったらしい。 仮にこれが CoffeeScript だったとすると、
opt.getOrElse(-> sideEffectFunction())
と書くことが出来ただろう。 しかしないものねだりをしてもしょうがない。
もうすこし考えてみたところ、macro 機能を使えば scala ぽいことができることに気がついた。 Macro はコンパイル時に実行されるコードで、プログラムを変更することができる。 Macro というと「怖い。黒魔術!」と身構えてしまうかもしれないが、ひどく怖いわけではない。
プログラムをコンパイルするときはテキストを読み込んで抽象構文木を作る。 C の macro はテキストの段階で置き換えを行うが、 Haxe の macro は抽象構文木の段階で変換を行う。 C では C の文法ではないテキストを macro で処理することができる。 Haxe では、まず抽象構文木を作る必要があるので、元々存在しない文法の文字列は macro を使ってどうこうできない。 Haxe の macro 説明ページでは 「文法が崩れないので解読不能にならない。嬉しいよね!」と主張している。 なお、Scala の macro も Haxe と同じ。 Wikipediaによると それぞれ Text substitution macros と Syntactic macros という名前で呼ばれるらしい。
Macro を使って書いたオレオレ getOrElse()
は以下のとおり。
import haxe.macro.Expr;
macro public static function getOrElse(opt: Expr, els: Expr) {
return macro switch ($opt) {
case Some(v): v;
case None: $els;
}
}
関数宣言の頭に macro
と付けることで、getOrElse()
が macro であることを示している。
Macro 引数の型は基本的に haxe.macro.Expr
型であり、これは抽象構文木上の式を表す。
木上で式を式に変換するわけだから、戻り値も Expr
型だ。
return
の次にある macro
は、以降の式を Expr
に変えてくれる便利なもの。
opt
や els
の参照は、$
prefix を付けて行う。
正直、まだ完全にわかった気はしていないが、 getOrElse()
の定義はこれで十分。
呼び出し方は scala と同じ
opt.getOrElse(sideEffectFunction());
となる。
getOrElse()
は macro なので、上の呼び出しはコンパイル時に以下に展開される。
// opt.getOrElse(sideEffectFunction()); が以下に置き換えられる。
switch (opt) {
case Some(v): v;
case None: sideEffectFunction();
};
展開結果を見るとわかるように opt
が Some(v)
の時には sideEffectFunction()
は評価されない。やった。Scala ぽくなった。
なお、macro 引き数の els
は macro 定義内で何度でも参照でき、各々が sideEffectFunction()
に展開されるため、評価回数が複数回になりうるというところは call-by-name と同じだ。
むぐぐ。
たしかに switch
に展開するだけなので els
に対する型の制約はない。
Macro でなんとか出来そうな気もするが将来の課題ということに。
Callback での不満点は無名関数の記法が長いということだったので、macro でそれを解決すれば満足できるかもしれない。
ただし Haxe の文法にないものは macro でどうしようもないので -> sideEffectFunction()
のような書き方はできない。
例えばこんな感じ。
macro public static function fn(e: Expr) {
return macro function() { return $e; };
}
を mixin すれば callback 版 getOrElse()
は以下のように呼び出せる。
opt.getOrElse(sideEffectFunction().fn());
ここが妥協点かなあ。
僕の問題(苦手)意識は The JavaScript Problem によく表されている。
もうちょっとマシな言語で書きたいと常々思っていた。
AltJS のなかで TypeScript は触ったが、Haxe は触っていなかった。 理由は「Haxe が吐き出す JavaScript はとても人間が読めるものじゃない」とどこかで聞いていたから。 いざって時には生成された JavaScript が読める形じゃないと困る。
今回ふとしたきっかけで Haxe を触ってみた。 さわってみるとかなり好感触。 出力された JavaScript はそんなに変なことになってなくて普通に読める。 やはり自分で触ってみないとダメだな。 代数的データ型があるなど TypeScript より型機能が強い感じ。
本題。
Scala でいうところの scala.Option
に対応する型として Haxe には haxe.ds.Option
という enum がある。
ただし Scala の case object と違い、Haxe の enum にはメソッドが定義できない。
最初は switch case
を使って Option の分岐を書いていた。
var opt: Option<Int> = ...;
switch(opt) {
case Some(v): trace(v);
case None: trace('None');
}
的な。 これでも悪くないが Scala ぽく書きたかったので、それっぽくできないか考えてみた (きっと他の人がどこかでやってる気がするけど)。
Haxe の using mixin を使えば同じようなことができそう。 次のようなものを作って
import haxe.ds.Option;
class OptionOp {
public static function map<A,B>(opt: Option<A>, f: A -> B): Option<B> {
return switch (opt) {
case None: None;
case Some(v): Some(f(v));
};
}
public static function newOption<A>(arg: A): Option<A> {
return if (arg != null) Some(arg) else None;
}
}
こんな感じに使う。
import haxe.ds.Option;
using OptionOp;
var x = 123.newOption(); // Scala の Option(123) 的な。
x.map(function(v) { return v * 2; }); // Scala の x.map(_ * 2) 的な。
他にもいろいろ追加したのがこちら。
関数リテラル書くのに毎度 function
と書くのは長いが、まあまあ快適に書ける。
コンパイルするとこんな感じになる。
var x = OptionOp.newOption(123);
OptionOp.map(x,function(v) {
return v * 2;
});
「えー。関数の呼び出しが多くて遅そう」という人は inline
化することもできる。
map()
, newOption()
の定義を public static inline function ...
とするとこんな感じの出力に変わる。
var x;
x = haxe.ds.Option.Some(123);
switch(x[1]) {
case 1:
haxe.ds.Option.None;
break;
case 0:
var v = x[2];
haxe.ds.Option.Some(v * 2);
break;
}
Some()
関数の呼び出しは残っているものの、 map
関数とその第二引き数で渡した関数の呼び出しは展開された。
これくらいなら速度的にも許容出来る人が多いんじゃない?
読みにくいから僕は inline 化しないけど。
Haxe でどうすればいいのかなと思っているのは Scala の for に相当するもの。 複数の Option があるとき全てが存在するときに何かをするには、 Scala の for を使えば
for (v1 <- opt1; v2 <- opt2) yield { v1 と v2 の計算 }
みたいに書けるのだが、現在の自分の Haxe 知識レベルだと次のようになる。
opt1.flatMap(function(v1) {
return opt2.map(function(v2) { return v1 と v2 の計算 });
});
なんか上手い書き方無いかなあ。 monax 使えばうまくかけるんだろうか?
JavaScript にアレルギーがあるひとは Haxe 触ると癒されるかも。
]]>ここでは R についてきている iris データセットを使う。 irisはアヤメの種類に関するデータセットで、1936年という大昔に フィッシャー が論文で使った歴史のあるデータセットでもある。 萼片 (sepal) の大きさ、花弁 (petal) の大きさ、アヤメの種類(species)が対になっている。
# iris dataset 読み込み
> data("iris")
> str(iris)
'data.frame': 150 obs. of 5 variables:
$ Sepal.Length: num 5.1 4.9 4.7 4.6 5 5.4 4.6 5 4.4 4.9 ...
$ Sepal.Width : num 3.5 3 3.2 3.1 3.6 3.9 3.4 3.4 2.9 3.1 ...
$ Petal.Length: num 1.4 1.4 1.3 1.5 1.4 1.7 1.4 1.5 1.4 1.5 ...
$ Petal.Width : num 0.2 0.2 0.2 0.2 0.2 0.4 0.3 0.2 0.2 0.1 ...
$ Species : Factor w/ 3 levels "setosa","versicolor",..: 1 1 1 1 1 1 1 1 1 1 ...
さて、random forest でアヤメを分類する。 手元にパッケージが入ってなかったのでそこから。
> install.packages("randomForest")
> library(randomForest)
> (fit <- randomForest(Species ~ ., data=iris))
Call:
randomForest(formula = Species ~ ., data = iris)
Type of random forest: classification
Number of trees: 500
No. of variables tried at each split: 2
OOB estimate of error rate: 4.67%
Confusion matrix:
setosa versicolor virginica class.error
setosa 50 0 0 0.00
versicolor 0 47 3 0.06
virginica 0 4 46 0.08
fit
に random forest で学習したモデルを代入した。
重要な説明変数は importance()
で取り出すことができる。
> importance(fit)
MeanDecreaseGini
Sepal.Length 10.499305
Sepal.Width 2.596317
Petal.Length 43.614641
Petal.Width 42.563816
どうやら花弁の大きさ(Petal.Length, Petal.Width)がアヤメの種類判定においては重要らしい。
varImpPlot(fit)
すると importance(fit)
と同じ結果をグラフにしてくれるようだ。
importance()
では重要な説明変数が分かったが、目的変数に対してどう効くのか、この例でいえば Petal.Length
が大きかったらどの種類になりやすいのか、分からない。
partialPlot()
を使うと、どう影響するかが分かる。
> partialPlot(fit, iris, Petal.Length, "setosa")
> partialPlot(fit, iris, Petal.Length, "versicolor")
> partialPlot(fit, iris, Petal.Length, "virginica")
これを見るに、Petal.Length がおおよそ3より小さい時には setosa、 3から5の時には versicolor、5より大きい時には virginica である可能性が高いことが分かる。
randomForest package の importance()
, varImpPlot()
, partialPlot()
を使えば効いてる説明変数を見つけたり、効いている方向を確認したりできる。
昔の Java におけるfor 文は、C と同じように for (初期化; 継続条件; カウンタ更新)
という形しかなかった。
Java 5.0 で導入された拡張for文(for (型 変数: コレクション)
の形)では java.lang.Iterable
を実装したオブジェクトならなんでも繰り返しができるようになった。
便利になったなあと思ったのを覚えてる(2004年の話)。
Scala で C の for 的なものをやろうとすると
// C では
for (int i = 0; i < 10; i++) { … }
// Scala では
for (i <- 0 until 10) { … }
となるので「 for (i <- 初期値 until 上限)
という固定形なんだー」と最初は思ったのだが、そうではなく for (i <- 初期値.until(上限))
であり for (i <- obj)
が for 式のパターンである。
ここで初期値.until(上限)
は Range
型の値を返す。
Scala の言語仕様によると for 式は以下の文法をもつ。
Expr1 ::= ‘for’ (‘(’ Enumerators ‘)’ | ‘{’ Enumerators ‘}’) {nl} [‘yield’] Expr
Enumerators ::= Generator {semi Enumerator}
Enumerator ::= Generator
| Guard
| ‘val’ Pattern1 ‘=’ Expr
Generator ::= Pattern1 ‘<-’ Expr [Guard]
Guard ::= ‘if’ PostfixExpr
Enumerator
の最後のパターンで ‘val’ Pattern1 ‘=’ Expr
とあるが、この val は deprecated になったようだ。
(Scala の言語仕様は更新が追いついていないらしく、現時点の最新(2.10.3)の言語仕様書は現時点で存在しない。)
Scala の for 式は foreach()
, map()
, flapMap()
, withFilter()
が実装されていればなんでも回せる。
全てが必要なわけではなく、用いられるパターンによって必要なメソッドが決まる。
foreach()
が必要なパターン。 yield
なしの時。
for (i <- obj) { … }
// 書き換えると
obj.foreach { case i => … }
map()
が必要なパターン。
Generator がひとつだけで yield
で値を返す時。
for (i <- obj) yield { … }
// 書き換えると
obj.map { case i => … }
flatMap()
が必要なパターン。
Generator が複数あり、yield
で値を返す時。
for (i <- obj1; j <- obj2) yield { … }
// 書き換えると
obj1.flatMap { case i => for (j <- obj2) yield { … } }
// obj1 には flatMap が必要。obj2 には map が必要。
withFilter()
が必要なパターン。
Guard があるとき。
for (i <- obj1 if i < 0) { … }
// 書き換えると
obj1.withFilter(i => i < 0).foreach { case i => … }
こんな感じで書き換えできるので、本質的には for 式要らないんだと思う。
でも複数の generator を回す時とか、foreach
や map
, flatMap
で書くとネストが深くなってしまうので、for文だとスッキリかけて嬉しいってのはある。
必要なメソッドさえ揃っていればいいので、繰り返し以外の文脈で用いることができる。
たとえば scala.Option
はオプショナルな値を表す。
値があるかもしれないし無いかもしれないという文脈であり、Javaでいえば null を使いたく場面で使う型である。
Option
では for 式に関連するメソッドが、値がある時には関数を実行し、そうでなければ何もせず値なしを結果とするという意味合いで定義されている。
for (i <- Some(1); j <- Some(2)) yield i + j // Some(3)
for (i <- Some(1); j <- None) yield i + j // None
上記のように「Option
型の値がいくつかあり、全ての値が存在している時に何かする」というのが for 文で実現できる。
他にも scala.concurrent.Future
は将来的に得られる値を表現する型で、
for式関連のメソッドは値が得られた時に関数を実行するように定義されている。
val f1: Future[Int] = …
val f2: Future[Int] = …
val f3: Future[Int] = for (v1 <- f1; v2 <- f2) yield v1 + v2
ここでは f1
, f2
の値が将来得られたら v1 + v2
を計算するという、コールバックの登録的な意味合いを持っている。
scala-arm では、最後にリソースを開放するという意味合いを for 式関連メソッドに持たせることで、for 式の最後でのリソース解放を実現している。
foreach
, map
, flatMap
, withFilter
というメソッドの定義次第で for 式の使い道は無限大。
書き方として A <: B と書けば A は B の subtype であることを示す。 A >: B と書けば A は B の supertype、言い換えれば B は A の subtype であることを示す。
Subtype とは何か?というのは Liskov substitution principle というので定義されている。 「A <: B であれば、B型の値に対して出来ることならなんでもA型の値に対して出来る」ということらしい。
クラス階層で言えば、 A <: B というのは A が B の subclass または A == B ってこと。
Subtype の概念はクラス階層にとどまらない。 こんな型が定義されていたら、
class Base
class Derived extends Base
type A = Base => Derived
type B = Derived => Base
A と B の間にクラス階層はないが、B がおける場所には A もおける。 つまり A <: B。 なぜなら
から。
関数の場合は一般に A2 <: A1 かつ B1 <: B2 のときに (A1 => B1) <: (A2 => B2) が成り立つ。 理由は上と同じ。
C[T]がパラメータ化された型で A, B が A <: B であるとき、C[A] と C[B] の関係は3パターン。
Scala ではタイプパラメタの前に +
とか -
とかつけて covariant や contravariant を表現する。何も付けなかったら invariant ね。
class C[+A] // C は covariant
class C[-A] // C は contravariant
class C[A] // C は invariant
Function object は covariant, contravariant を使ってる。
trait Function1[-T, +U] {
def apply(x: T): U
}
A => B
は Function[A, B]
と同じ。
で、Function1
の T は contravariant, U は invariant ということなので、
class A1
class A2 extends A1
class A3 extends A2
とあったときに A2 => A2
型の値は A3 => A1
型が求められるところならどこでも使える。
Java の配列は covariant。
class Base {…}
class Derived1 extends Base {…}
class Derived2 extends Base {…}
が定義されていてると以下の様なことができる。
Derived1[] a = new Derived1[]{ new Derived1() }
Base[] b = a
b[0] = new Derived2()
Derived1 s = a[0]
1行目は普通。
2行目は super class である Base
の配列に代入しようとしている。
a
には Base[]
型がきて欲しいが、Java の配列は covariant なので Base[]
が来れる場所には Derived1[]
が来ても良い。
なので2行目も通る。
3行目では b[0]
に値を代入している。
b[0]
の型は Base
。
Derived2
は Base
の subclass なので問題ない。
4行目では Derived1
型の a[0]
を別の Derived1
型の変数に代入している。
これも問題無さそう。
という訳でコンパイルは通る。
でも a
と b
は同じ配列を指していて、3行目で Derived2
型の値に入れ替えてる。
だから a[0]
(とb[0]
)には Derived2
型の値が入っているはず。
4行目ではそれを Derived1
型の値に代入している。
なにかおかしい。
Javaではコンパイルエラーにはならず、3行目実行時に ArrayStoreException が投げられる。 残念。
Java の(問題含みの)配列を scala 的に表現すると
class Array[+T] {
def update(x: T)
}
となる。 問題は covariant な型パラメータ T がメソッドの引数になっていること。
Scala のコンパイラはこの問題を防ぐために variant check というものを行う。 いろいろ細かいルールはあるそうだが、大まかには以下のとおり。
Function1 を見なおしてみるとルールに合致していることが確認できる。
trait Function1[-T, +U] {
def apply(x: T): U
}
contravariant な T はパラメタにきており、covariant な U は戻り値にきている。 問題なし。
Scala では immutable な collection は covariant, mutable な collection は invariant になっているらしい。 きっと Java の例にあるような実行時エラーをコンパイル時に捕まえるにはそうするしか無いんだろうな。
trait List[+T] {…}
object Nil extends List[Nothing] {…}
class Cons[T] extends List[T] {…}
みたいな感じ。
Nil
のときの T
は Nothing
。
Nothing
は全ての型の subtype(Nothing
<: なんでも)。
List は covariant なので List[Nothing]
<: List[なんでも]
となる。
どの T
の List[T]
に対しても Nil
を使えるので便利。
リストの先頭に要素を追加する prepend
メソッドを定義したい。
trait List[+T] {
def prepend(elem: T): List[T] = new Cons(elem, this)
}
一見これで良さそうだが、
error: covariant type T occurs in contravariant position in type T of value elem
というコンパイルエラーになる。
Variance check が活躍してる。
たしかに covariant な型パラメータは戻り値にしか使っちゃいけなかったんだ。
正しい定義はこうなる。
trait List[+T] {
def prepend[U >: T](elem: U): List[U] = new Cons(elem, this)
}
これは variance check を通る。U は contravariant で引き数のところに使われているから。
戻り値型は List[U]
だが U そのものじゃないので contravariant 扱いじゃないんだろう。
class Base
class Derived1 extends Base
class Derived2 extends Base
def f(xs: List[Derived1], x: Derived2) = xs.prepend(x)
さてこのとき f()
の戻り値型はなんだろうか?
prepend()
の戻り値なので List[U]
型になるはず。
xs
が List[Derived1]
型なので T
は Derived1
で決まり。
U
を決めるのに型推論が活躍する。
U
は x
の型なので Derived2
だろうか?
しかし Derived2
だとすると U >: T
が満たせない(Derived2
は Derived1
の supertype ではない)。
というわけで型推論さんは、Derived2
と Derived1
の共通の親である Base
が U
だと結論付ける。
したがって f()
の戻り値型は List[Base]
ということになる。
コード片を見ただけではわからなくなるので、暗黙的な記述はあまり使わない方がいいんじゃないかと個人的に思うのだけど。 それでも便利な使い道があるから使われているみたい。
今自分が知っているところで次のパターンがあるみたい。 他の便利な使い方もきっとあるんじゃなかろうか。
既存クラスに対してメソッドをあと付けしたいときに使う。 既存クラスが自分のメンテナンスできる範囲で書き換えてOKならこのパターン使わなくていいと思う。 既存クラスがサードパーティライブラリから提供されている場合など、書き換えられない、書き換えるのが面倒ときに使う。
やり方は 2.9 以前の場合は
(pimp my library pattern)で、2.10 以降の場合は implicit class が導入されたのでこれを使うんだと思う。
ここでは Int
型に関数 f1
をあと付けしたいとする。
pimp my library pattern ではまずラッパークラスを定義して、
class MyRichInt(x: Int) {
def f1 = …
}
暗黙的変換関数を定義する。
object MyRichInt {
implicit def intToMyRichInt(x: Int) = new MyRichInt(x)
}
使う時は暗黙的変換関数を import すれば、メソッドが増えたように感じる。
import MyRichInt._
123.f1 // new MyRichInt(123).f1 相当
Implict class 2.10 から導入された機能。 pimy my library pattern が簡単に書けるようになった感じ。 こんな感じで定義。
object Helpers {
implicit class IntWithF(x: Int) {
def f1 = …
}
}
使う時は import する。 既存クラスに存在しないメンバを呼び出した場合に、暗黙的に変換できるクラスにメンバがあれば、コンパイラさんが変換→呼び出しという風にしてくれる。
import Helpers._
123.f1 // new IntWithF(123).f1 相当
これでプログラマ的には既存の Int 型には無かったメンバ f1
が増えたかのように扱える。
関連する項目として
value class
がある。
これを一緒に使えば暗黙変換するとき new
されなくなる(メモリ割り当てされなくなる)ので
使えるときは使うのがいい。
頻繁に使われるものの場合は速くなりそう。
JVMは型パラメータをコンパイル時に消しちゃうので実行時には型パラメータの情報は使えない。
def f[A] = new A // 間違い
とかしたいときに困る。
そんなときは最後の引き数リストに implicit な ClassTag
を受け取るようにすればいいみたい。
def f[A](implicit c: ClassTag[A]) = c.runtimeClass.newInstance().asInstanceOf[A]
c
経由でインスタンスを作ったり出来る。
呼び出し時は c
を渡す必要はない。
f[Int]
などとして呼び出せる。
既存のクラス群が共通インタフェースを持ってたらひとつの関数で同じように処理できるのに、、ってときに使うパターン。 既存のクラスが書き換えられるなら、インタフェースを新規に作って実装しちゃってもいいんじゃないかと思わなくもないけど、アルゴリズムに関連する部分は分けておきたいなんてこともあるのかもしれない。
この共通インタフェースのことを concept っていうらしい。
Int
と String
が両方共 double
っていう関数を持っていたら、ステキなアルゴリズム(関数)がかけるのになあ、とする。
trait DoubleConcept[A] {
// 共通インタフェース
def double(v: A): A
}
implicit val doubleInt = new DoubleCondept[Int] {
// Int の double 定義
def double(v: Int) = v * 2
}
implicit val doubleString = new DoubleConcept[String] {
// String の double 定義
def double(v: String) = v + v
}
def suteki[A](v: A)(implicit c: DoubleCondept[A]) = … // c.double(v) を使ったステキアルゴリズム
suteki(123) // suteki(123)(doubleInt) 相当
suteki("ABC") // suteki(ABC)(doubleString) 相当
型ごとの共通インタフェース実装を implicit val として定義しておいて、 implicit なパラメータリストでそれを暗黙的に渡す。 暗黙的過ぎて難しい。
ともあれ suteki
関数のなかでは Int
と String
が両方共 double
という共通操作を持っているという前提で関数がかける。
suteki
関数呼び出し時の v
引き数の型により、c
が doubleInt
なのか doubleString
なのかはコンパイラが選択してくれる。
Scala の implicit は黒魔術。 使いたくなるパターンは多くなさそうなので用途を抑えておけば理解しやすい。
]]>テストケース間でデータを共有したいときがある。
たとえば以下の例では data1
と data2
が共通なので共通化したくなる。
class FooSuite extends FunSuite {
test("test A") {
val data1 = new FooData(…)
val data2 = new FooData(…)
// tests with data1 and data2
}
test("test B") {
val data1 = new FooData(…)
val data2 = new FooData(…)
// another tests with data1 and data2
}
}
普通は以下のようにメンバ変数にしたくなると思う。
class FooSuite extends FunSuite {
val data1 = new FooData(…)
val data2 = new FooData(…)
test("test A") {
// tests with data1 and data2
}
test("test B") {
// another tests with data1 and data2
}
}
この方法は少し問題がある。
new FooData(…)
が例外を出した場合に、FooSuite
のインスタンス化に失敗するという問題があるFooData
が mutable な場合に、前に実行したテストの内容により結果が変わる可能性がある課題のテストケースでは trait を使って以下のようにしていた。
class FooSuite extends FunSuite {
trait TestData {
val data1 = new FooData(…)
val data2 = new FooData(…)
}
test("test A") {
new TestData {
// tests with data1 and data2
}
}
test("test B") {
new TestData {
// another tests with data1 and data2
}
}
}
各テスト内で TestData
trait を継承した無名クラスを作り、無名クラスのコンストラクタ内でテストを実行する。
こうすることで new FooData(…)
の実行はテスト実行時になり、各テストごとにデータが初期化されるので、上にあげた問題が解消する。
Sharing fixtures をみると他の方法もいろいろある。 一番上にある Calling get-fixture methods が一番単純ぽい。 この方法で例を書き換えるとこうなる。
class FooSuite extends FunSuite {
def fixture = new {
val data1 = new FooData(…)
val data2 = new FooData(…)
}
test("test A") {
val f = fixture
// tests with f.data1 and f.data2
}
test("test B") {
val f = fixture
import f._
// another tests with data1 and data2
}
}
無名クラスのメンバとしてテストデータを作る。
こっちのほうがインデント少なくていいかも。
import f._
すれば f.
prefix 要らないしね。
new
のあとクラス名無くてもコンパイル通るんだ… 知らなかったよ。
Rails で thin controller, fat model を心がけていると、model がマジで激太りしてヤバくなる。 実際に自分が仕事で書いている rails アプリも激太りしててヤバい。 この blog の筆者が作っている CodeClimate で C 判定をもらう程度には肥満体型になっている。
Model が太ってきた時に考えるのは ActiveSupport::Concern
を使って感心事を抜き出して、Mixin にすることだと思う。
実際に手元のアプリでも models/concerns/
なんていうディレクトリがあったりする。
でもこれはアンチパターン。 Mixin は継承と同じように複雑さを増す。 やってみると、さほど綺麗に分けられないんだよね。 記事では mixin はするな、と書いてある。
ここからがパターンの紹介。
Value object は値が等しければ等しいとされるようなオブジェクトで、だいたいは immutable。 この value object を抜き出して、関連するロジックを抜き出したクラスに移す。
どんなときに使うか? 属性に強く結びついたロジックがあるとき。
例として挙げられているのは、電話番号、お金など。
CodeClimate には AからF の値をとる Rating
value object があるらしい。
class Rating
include Comparable
def self.from_cost(cost)
if cost <= 2
new("A")
elsif cost <= 4
new("B")
elsif cost <= 8
new("C")
elsif cost <= 16
new("D")
else
new("F")
end
end
def initialize(letter)
@letter = letter
end
def better_than?(other)
self > other
end
def <=>(other)
other.to_s <=> to_s
end
def hash
@letter.hash
end
def eql?(other)
to_s == other.to_s
end
def to_s
@letter.to_s
end
end
cost
から rating を計算するロジックと、比較ロジックがこのクラスに抜き出されている。
#hash
, #eql?
を定義しておくと、hash key として使うことができる。
ActiveRecord 側。
rating
は DB に保存された値から計算される値みたい。
class ConstantSnapshot < ActiveRecord::Base
# …
def rating
@rating ||= Rating.from_cost(cost)
end
end
Value object として抜き出せるものがあれば、間違いなく抜き出したほうがいいね。
以下の基準の1つ以上に合致したときには service object の抜き出しを薦めている。
以下の例はユーザ認証を行う service object。
class UserAuthenticator
def initialize(user)
@user = user
end
def authenticate(unencrypted_password)
return false unless @user
if BCrypt::Password.new(@user.password_digest) == unencrypted_password
@user
else
false
end
end
end
ここで user
がモデルで、認証というアクションを service object として抽出している。
結局のところ「複雑なメソッド(群)を見つけたら別のクラスにしましょう」ということかな。
フォームの submit で複数のモデルが更新される場合などに使うパターン。
以下の例は User
と Company
の両方を更新する。
class Signup
include Virtus
extend ActiveModel::Naming
include ActiveModel::Conversion
include ActiveModel::Validations
attr_reader :user
attr_reader :company
attribute :name, String
attribute :company_name, String
attribute :email, String
validates :email, presence: true
# … more validations …
# Forms are never themselves persisted
def persisted?
false
end
def save
if valid?
persist!
true
else
false
end
end
private
def persist!
@company = Company.create!(name: company_name)
@user = @company.users.create!(name: name, email: email)
end
end
Signup
クラスは Virtus を使うことで、
ActiveModel のように属性を持つことができる。
ActiveModel::Naming
と
ActiveModel::Conversion
は、つけとくと良いことがあるみたい。
ActiveModel::Validations
を include することで validation も掛けられる。
複雑なクエリを発行するときは query object を使うといいかも。 放置されたアカウントを探す query object の例。
class AbandonedTrialQuery
def initialize(relation = Account.scoped)
@relation = relation
end
def find_each(&block)
@relation.
where(plan: nil, invites_count: 0).
find_each(&block)
end
end
コンストラクタに渡されているのは ActiveRecord::Relation
のインスタンス。
他の条件がついた relation を渡すと、クエリを組み立てることができる。
old_accounts = Account.where("created_at < ?", 1.month.ago)
old_abandoned_trials = AbandonedTrialQuery.new(old_accounts)
この手のクラスは隔離した状態でのテストを頑張らず、DBにアクセスする形でテストしたほうがいい。
表示に限ったロジックを書いている場合には、それを抜き出すことを考える。 「もし別のインターフェース(例えば音声コントロールのUI)を実装するときに必要かな?」と考えて No なら view object が向いてる。 以下は Code Climate のドーナツチャートの例。
class DonutChart
def initialize(snapshot)
@snapshot = snapshot
end
def cache_key
@snapshot.id.to_s
end
def data
# pull data from @snapshot and turn it into a JSON structure
end
end
読み込みポリシーに特化したオブジェクト。 ビジネスルールをひとつカプセル化する。
Service object に似ているが、service object は操作、 policy object は読み込みを担当。 Query object にも似ているが、query object は SQL 発行、 policy object はメモリ上のドメインモデル読み込みを担当する。
class ActiveUserPolicy
def initialize(user)
@user = user
end
def active?
@user.email_confirmed? &&
@user.last_login_at > 14.days.ago
end
end
上記の例は、だれが “active?” か、という定義を与えている。
既存の機能の上に機能を足したいときに使うパターン。 たとえばコメント書き込み時に、Facebook wall へもポストするような時に使う。
class FacebookCommentNotifier
def initialize(comment)
@comment = comment
end
def save
@comment.save && post_to_wall
end
private
def post_to_wall
Facebook.post(title: @comment.title, user: @comment.author)
end
end
]]>最初にまとめ。
key := value
は新しい設定項目を追加する関数のようなもの(Setting[T]
)を定義する。Setting[T]
の入力は変更されない。.sbt
を読み込むと Setting[T]
のリストができる。Setting[T]
のリストは、依存関係を考慮してソートされた後に適用される。.sbt
の空行で区切られた塊は Scala の式。文ではないので val
, object
, class
などは書けない。key := value
は key.:=(value)
といったメソッド呼び出しを別の書き方にしたもの。sbt.Keys
に定義されている。TaskKey[T]
は毎回計算されるキー。SettingKey[T]
, TaskKey[T]
などを呼び出す際の第一引き数文字列を使う。inspect
で確認できる。Setting[T]
は、設定を入力に与えると、新しいキーと値のペアを追加したり、既存のキーを新しい値で追加したりといった変換を表す。
変換っていうのは、なにかを入力されたら、それを変更した結果を返すということで、関数みたいな概念で捉えればいいのだろう。
Setting[T]
は関数型の精神に則り、入力値は変更しない。
.sbt
ファイル中の key := value
は変換(Setting[T]
)を定義している。
たとえば name := "hello"
とあれば、キーがname
で値が"hello"
のペアを追加・上書きする変換を定義する。
T
は設定値の型なので、この場合は Setting[String]
が定義されたことになる。
.sbt
ファイルには変換が沢山含まれているので、結果として変換(Setting[T]
)のリストができる。
Setting[T]
のリストに、sbt のデフォルトの設定を入力すると、.sbt
ファイルが反映された設定が得られる。
Setting[T]
が入力値を変更しないのでsbtのデフォルトの設定は変更されない。
.sbt
ファイルで name := "hello"
と書いたら、name.:=("hello")
のこと。
key
の型は SettingKey[String]
で、 :=
は String
を引数にとり Setting[String]
を返す。
間違った型の値を代入しようとした場合には(例:name := 1
)、型チェックでエラーになる。
:=
メソッドの他にも +=
(リストへの要素追加)などがある。
sbtに用意されている設定項目(キー)は
sbt.Keys
object に SettingKey[T]
型で定義されている。
.sbt
ファイルの内容が評価される時は import sbt.Keys._
された状態なので、 sbt.Keys._
はパッケージ修飾なしに参照できる。
sbt.Keys
は重要。
設定項目を探すときにちょくちょく参照することになる気がする。
たとえば name
はこんな感じ。
val name = SettingKey[String]("name", "Project name.", APlusSetting)
前述のようにSettingKey[String]
であることがわかる。
sbt.Keys
には SettingKey[T]
型以外にも、 TaskKey[T]
, InputKey[T]
, AttributeKey[T]
型の値が定義されてる。
compile
のように sbt コンソールから入力するたびに実行してくれないと困るものは
TaskKey[T]
として定義されている。
たとえば compile
はこんな感じ。
val compile = TaskKey[Analysis]("compile", "Compiles sources.", APlusTask)
SettingKey[T]
は一度計算されると結果が保持される。
TaskKey[T]
は毎回計算される。
SettingKey[T]
はTaskKey[T]
に依存できない。
InputKey[T]
, AttributeKey[T]
はまだ知らない。
sbtコンソールから入力するのはキーコンストラクタの第一引き数の文字列。
val scalacOptions = TaskKey[Seq[String]]("scalac-options", "Options for the Scala compiler.")
とあったら scalac-options
がsbtコンソールから入力時に指定する文字列になる。
sbt.Keys
に CamelCase で定義されたものは大体コンソール入力時には小文字、ハイフン区切りになっている。
キーに関する情報はsbtコンソールから inspect キー名
で得られる。
.sbt
ファイルの先頭に import
文を複数行書ける。import
文の間は空行をあけなくていい。
他の SettingKey[T]
を参照するときに使うのかな。
akka.actor.Actor
。
メッセージを受けて処理をする人。
Actorへのメッセージが溜まるところ。
Actor間の依存関係のこと。 Actorインスタンスはツリー構造になっている。 親が supervisor となる。
Actor が失敗したらどうするかは supervisor の actor が制御する。
親 actor が shutdown したら、その子どもは全て shutdown する。
タスクのまとまり毎にサブツリーを形成するようにすると良いらしい。
Actorインスタンスにはツリー構造上の位置を表す文字列がついており、これを actor path と呼ぶ。
/
, /user
, /user/abc
など。
akka.actor.ActorSystem
。
ツリー全体を管理するもの。
ActorSystemはスレッドを何本も使う重い構造なので沢山作りすぎない。
Actor の一種。メッセージを受け取って他の actor に投げる(Routingする)。 投げられる側を routee と呼ぶ。 Routee は router の子どもになる。
以下の様な router が提供されている。どのようにルーティングするかは名前から大体想像がつく。 ロードバランサ的なやつですかね。
akka.actor.Props
。
Actorをインスタンス化する際の設定。
Immutable なので複数の actor をインスタンス化する際に使いまわせる。
生まれてから死ぬまで。
停止信号を受け取ると以下の手順で止まる。
postStop()
を呼び出すDeathWatch
に停止完了を知らせるcontext.stop(self)
, context.stop(child)
で止める。以下のいずれか。
失敗は3つのカテゴリに分けられる。
内部状態が壊れたら内部状態を破棄しなければならない。Supervisorや他の子どもが壊れた内部状態の影響を受けなければ、restartするのがベスト。 Restartの際には内部的に新しい Actor がインスタンス化され、ActorRefの参照しているものが切り替わる。 ActorRef の存在意義のひとつは、この切り替え。
Actorが内部で作った子 Actor の ActorRef を外部に保持していると、 Actor restart 時に無効な参照になる。
ガーディアン直下の actor に対応する ActorRef は ActorSystem 終了時まで有効だろうと思う。
]]>build.sbt
に追加。
libraryDependencies ++= Seq(
"org.scalatest" %% "scalatest" % "1.9.1" % "test",
"org.mockito" % "mockito-core" % "1.9.5" % "test"
)
テストで MockitoSugar
を mixin し、以下の import 行を追加。
import org.mockito.Matchers._
import org.mockito.Mockito._
// ClassToMock のモックを作成
val m = mock[ClassToMock]
作ったモックはデフォルトで全メソッドコールに対して null
を返す。
メソッドの戻り値としてnull
ではなくモックを返したい時は次のようにする。
// メソッド呼び出しでモックを返すモック
mock[ClassToMock](RETURNS_MOCKS)
// 返されたモックもモックを返すモック
mock[ClassToMock](RETURNS_DEEP_STUBS)
when(メソッド呼び出し).thenReturn(戻り値)
で登録する。
メソッド呼び出し部の引き数には any
で任意の型の任意の値に、anyString
, anyBoolean
, anyByte
, anyChar
などで基本的な型の任意の値に anyVararg
で任意の可変引き数にマッチさせることができる。
// m.method1(何か) が呼ばれたら 10 を返す
when(m.method1(any)).thenReturn(10)
引数が2つ以上あり、いずれかで any
などのマッチャーを使った場合には、他の引き数もマッチャーにする必要がある。
オブジェクトが等しいことを示すマッチャーは eq(obj)
で作れる。
後から登録したものが先にマッチするので、条件のゆるいもの(マッチ範囲が広いもの)を先に書く。
thenReturn()
の可変引き数版を使う
// 最初は 10, 次は 20, それ以降はずっと 30 を返す
when(m.method1()).thenReturn(10, 20, 30)
// method1() が呼ばれたら例外を起こす
when(m.method1()).thenThrow(new RuntimeException("Gau gau"))
thenReturn
と同じように1回目、2回目で違う例外を起こすことも可能。
むしろ thenReturn
と組み合わせることができる。
when(m.method1())
.thenReturn(10, 20) // 1回目は10, 2回目は20を返す
.thenThrow(new FooException, new BarException) // 3、4回目は例外
.thenReturn(30) // 5回目以降は30を返す
mock[ClassToMock]
はフルのモック。一部だけモックしたい場合はこっち。spy
と呼ぶ。
// obj は普通のオブジェクト
val s = spy(obj)
// s.method1(何か) が呼ばれたら obj の実装を使わずに 10 を返す。
doReturn(10).when(s).method1(any[ClassOfArg])
thenReturn
が doReturn
になって順番が代わり、メソッド呼び出しも when
の外に出す。
thenReturn
の書き方では実際のメソッドが呼ばれるので、呼ばれては困るときにこちらの書き方をする。
この記法の場合は any
を使うときに型を指定しなければならなかった
(理由はわかってない)。
when()
, thenReturn()
, doReturn()
などは関数呼び出しの前に用意する。
用意したものがテスト中で呼び出されなくても問題ない。
呼び出されたことを確認するには verify()
を使う。
// テストを実行
m.method1(123);
// method1() が何らかの整数引き数で呼び出されたことを確認
verify(m).method1(anyInt);
複数回呼び出されたことを確認するときは、verify()
の第二引き数で回数指定をする。
// 2回呼び出されたことを確認
verify(m, times(2)).method1(anyInt)
// 少なくとも2回
verify(m, atLeast(2)).method1(anyInt)
// 多くとも3回
verify(m, atMost(3)).method1(anyInt)
never
を使う。
// 一度も呼ばれてないことを確認
verify(m, never).method1(anyInt)
Mockitoのドキュメント に書いてある。
]]>と開き直ってもしかたがないので Haskell の入門書として評価が高そうな「すごい Haskell たのしく学ぼう!」 を購入した。 Kindle版があるのは素晴らしい。
読書メモを残しておく。今日はイントロを読んだ。
語り口が柔らかで読みやすい。
]]>まず、既存のプロパティをそのまま universal analytics へ移行することは出来ない。 Google Analytics のプロパティを新しく作る必要があった。
次にするのは JavaScript スニペットの更新。
今までは ga.js
を読み込んでいたが、これが analytics.js
に変わる。
イベント送信時の syntax も変わる。
この blog で使っている octopress では、analytics.js
に対応していない。
こんな感じ
でちょいちょいと変更したところ無事に動いているようだ。
セッションのタイムアウト時間が設定できるようになっていたり、できることが増えているようだが、まだ良くわかっていない。 ともあれ切り替えは無事にできた。
]]>