いつもは IntelliJ IDEA + Scala プラグインで Scala の開発をしていますが、ファイルによってすごくもっさりするので、他の開発環境はどうだろうかと試してみました。
Scala 2.10 からの型クラス2(Functorはどうする?)
前回の記事にコメントをいただいて、Functor とか Monad なんかの高階型はどうするのか改めて考えてみました。
結論から言うと Scala は関数リテラルで型パラメータとれないので、関数リテラルだけでなんとかしようというのは不可能っぽいです(いまんところの私の頭では)。
しかし、implicity 使ったり context-bound を使わなくてもなんとかなりそうです。ここまで来ると型クラスじゃねーだろっ。と言われるかもしれませんがアドホック多相にはなってると思います。
ということで、Functor をやってみます。Haskell だとこんな感じですね。
class Functor f where fmap :: (a -> b) -> f a -> f b |
Scala だと
import language.higherKinds trait Functor[A] { type F[_] def fmap[B](func: A => B): F[B] } trait FunctorFunctions { def fmap[A, B](func: A => B)(f: Functor[A]): m.F[B] = f.fmap(func) } |
Option 型に適用してみます。
trait FunctorInstance { implicit class OptionFunctor[A](option: Option[A]) extends Functor[A] { type F[_] = Option[_] def fmap[B](func: A => B): Option[B] = option.map(func) } } |
使い方。
val allInstances = new FunctorInstance with FunctorFunctions {} import allInstances._ val maybeInt = Option(3) val func: Int => Int = _ * 2 println(maybeInt.fmap(func)) println(fmap(func)(maybeInt)) // こんな風にも使えます def doubleInt(f: Functor[Int]): f.F[Int] = f.fmap(func) println(doubleInt(maybeInt)) |
def は撲滅できませんでしたが、context-bound が撲滅できるのが私個人としては嬉しいです。それにやっぱりコード量はこっちのほうが少ないし!
でも、この fmap、カリー化を用いた部分適用(という言い方でいい?)できない! ぎゃふん!
Scala 2.10 からの型クラス
Scala で型クラス使おうという人ならば一度は目を通したことがあるだろう「空飛ぶサンドイッチのパーツ」。
Scala での型クラスの実装方法といったらたいていこの記事に書いてあるような方式になるのではないでしょうか(というか、私がググった限りではこのパターンしか見つけられなかった)。
でもこの方式、context-bound 型パラメータ使うんですよね。これのせいで def が登場しちゃうし、カリー化ができない。空飛ぶサンドイッチのパーツ自身、関数リテラル使おうぜ! と言いながらこのせいで def 使っちゃう。悔しい。それから、もっと便利にしようということで Scalaz みたいに Ops とか入れちゃって、型クラス実現するのにめんどうなこと!
ということで、Scala 2.10 から入った implicit classes 使うと型クラス簡単ですぜっていうのが今回の話です。空飛ぶサンドイッチのパーツの Depth をやってみます。
まず型クラス。ジェネリクスはいりません。
trait Depth { val: depth: Int } // お好みで関数バージョンも作りましょう trait DepthFunctions { val depth: Depth => Int = _.depth }
そして型インスタンス。
trait DepthInstances { implicit class TreeDepth(tree: Tree) extends Depth { val depth: Int = tree match { case Empty() => 0 case Leaf(_) => 1 case Node(l, r) => 1 + math.max(l.depth, r.depth) } } implicit class ListDepth(list: List[Int]) extends Depth { val depth: Int = list match { case xs => xs.size } } }
使い方。空飛ぶサンドイッチのパーツでは、def になっていた halfDepth もこれで晴れて関数リテラルに!
val allInstances = new DepthInstances with DepthFunctions {} import allInstances._ val halfDepth: Depth => Int = _.depth / 2 // ↓ 関数バージョンを使えばこんな書き方もできます。 // val halfDepth: Depth => Int = d => depth(d) / 2 halfDepth(List(1, 2, 3, 4))
簡単! やった!
Xcode 5.0 で不調になった Haskell 環境をなんとかする
iOS 7 のリリースに合わせて Xcode 5.0 がリリースになり、思わずアップデートしてしまった人はきっと私だけじゃないはず。しかし、これによって私の Haskell 環境は調子が悪くなってしまいました。特に Haskell コード中のシングルクォートの扱いがおかしいケースが目立ちました。
なお、私の Haskell 環境は Homebrew でインストールしたものです。
GHC に使わせる gcc を変更する
問題の原因は、Xcode 5.0 付属の gcc のようです。そこで、gcc のバージョン 4.7 を別途インストールします。
brew tap homebrew/versions brew install gcc47 |
GHC の設定を変更するため /usr/local/Cellar/ghc/7.6.3/lib/ghc-7.6.3/settings
を修正します。
2 | ("C compiler command", "/usr/bin/gcc"), |
↑これを↓このように修正します。
2 | ("C compiler command", "/usr/local/bin/gcc-4.7"), |
これでだいたい良さそうでしたが、これだけでは Yesod が依存している entropy のコンパイルが通りませんでした。どうも単純にパス上の gcc を見ているくさいのです。ですので、Homebrew を使っているのでしたら、/usr/bin
よりも /usr/local/bin
を優先してパスを通しているかと思いますので、ここにシンボリックリンクを作成してしのぎましょう。
ln -s /usr/local/bin/gcc-4.7 /usr/local/bin/gcc |
ただしこれをやると、ほとんど Xcode の gcc をパス上から隠蔽することになるわけですから、その点十分に注意してください(コマンドラインで Xcode に依存するような開発をする場合など)。
それから、haskell-platform も念の為に再インストールしたほうがいいかもしれません。
brew uninstall haskell-platform brew install haskell-platform |
Yesod-Auth の HashDB を使うサンプルできたよ
日々のすきをみてドン亀のごときペースで Haskell の勉強をしているのですが、入門書片手に学ぶのも飽きてきたので、実際になんか作ってみようとしています。
そこで Haskell の Web アプリケーションフレームワーク「Yesod」に挑戦しています。
Yesod はデフォルト状態(プロジェクトを作成直後の状態)ですでにログイン認証の仕組みが使えるようになっているのですが、この認証方式が Google の仕組みを使う一種のシングルサインオン方式になっているのです。これはこれで技術的に興味深いのですが、しかしじゃあ実際にどれぐらい使うんだというと、実案件ではそうそう使うものではないと思います。実際によく使う方式といえばやはり DB にユーザ名とパスワードを保存しておき、入力値がそれにマッチするかを見る方式が多いのではないでしょうか?
Yesod には標準で Yesod-Auth という認証の仕組みがあって、このサブパッケージによって前述の Google を使った認証方法や BrowserID を使った認証方法など様々な認証方法を提供しています。そして当然 DB を使った認証方法も提供しています。それが Yesod.Auth.HashDB パッケージ(以下 HashDB)です。
しかし、これを使うためのドキュメントでわかりやすいのがなかなか見つからないのです! 英文のリーディング自体がたどたどしい Haskell と Yesod の初学者にはイバラの道以外の何物でもないような状態だと私は感じました。しかしなんとか最低限何をすればいいのかまとめることができましたので紹介します。
なお、今回使用した Yesod のバージョンは 1.2.3.3 です。この記事に関するソースコードは Github で公開しています。diff を見てみるとどこに手を付けたのかわかりやすいかもしれません。
手を付けるべきファイルは3つ
そもそもデフォルトの状態から最低どのファイルに手をいれるべきかもなかなか定かにならなかったのですが、以下の3つが修正対象です。
- config/models
- Models.hs
- Foundation.hs
config/models
まず、デフォルトでもユーザ情報を格納するためのテーブル定義を行っているのですが、HashDB で使用するには若干手直ししなければなりません。ポイントは以下の点です。
- パスワードは必須項目でなければなりません。
- salt カラムが必要です。
デフォルトでは password カラムに Maybe がついているのでパスワードが必須項目になっていません。また、HashDB ではパスワードは SHA-1 でハッシュ化して扱います。その際に salt の値を使います。具体的な例をあげると、パスワードが “secret”、salt が “shio” だとすると “shiosecret” をハッシュ化した値を使用するわけです。この salt を保存するカラムが必要になります。
以上を踏まえると、config/models の User 定義は以下のようになります。
1 2 3 4 5 6 | User ident Text password Text salt Text UniqueUser ident deriving Typeable |
Model.hs
config/models の定義によってモデルが User 型が定義されますが、HashDB で使用するには HashDBUser 型クラス制約を満たさなければなりません。それのための修正を Model.hs で行います。まず、HashDB を Import します。
5 | import Yesod.Auth.HashDB (HashDBUser(..)) |
次に型インスタンスの作成です。
17 18 19 20 21 22 | instance HashDBUser User where userPasswordHash = Just . userPassword userPasswordSalt = Just . userSalt setSaltAndPasswordHash s h p = p { userSalt = s , userPassword = h } |
Foundation.hs
最後の Foundation.hs で認証方式を設定します。まず、BrowserID 認証方式と GoogleEmail 認証方式を使用しないので、以下の Import は削除してかまいません。
7 8 | import Yesod.Auth.BrowserId import Yesod.Auth.GoogleEmail |
かわりに HashDB のための Import を追加します。
7 | import Yesod.Auth.HashDB (authHashDB, getAuthIdHashDB) |
getAuthId と authPlugins を修正します。
124 125 126 127 | getAuthId = getAuthIdHashDB AuthR (Just . UniqueUser) -- You can add other plugins like BrowserID, email or OAuth here authPlugins _ = [authHashDB (Just . UniqueUser)] |
以上で完了です。
その他、使い方等は Github にあげている readme.md を参照してください。