社内Haskellチュートリアルのススメ
こんにちは。インフラストラクチャ本部の竹辺(@beketa)です。
このエントリはGREE Advent Calendar 2013 12日目の記事です。
1. グリーでのHaskellプログラマ採用
Haskellを導入する企業が増えているようです。最近国内のメディアで紹介された事例だけでも
- Tsuru Capital様: http://itpro.nikkeibp.co.jp/article/Watcher/20131003/508622/
- NTTデータ様: http://itpro.nikkeibp.co.jp/article/NEWS/20131126/520642/
の複数があり、すっかり実用的なプログラミングとして定着した感があります。
弊社でも2012年の中ごろから複数のプロジェクトでHaskellを使い始めており、昨年からは一部の商用サービスでもHaskellで開発したミドルウェアを使用しています。これらの事例についてはCEDEC、CUFPなどのカンファレンスでも発表いたしましたのでご興味のある方はそちらをご覧ください。
- CEDEC http://cedil.cesa.or.jp/session/detail/1070
- CUFP http://www.slideshare.net/greetech/20130923-cufp http://www.youtube.com/watch?v=cnqZyvp76Xw
さて、弊社のような企業でHaskellを用いるメリットは何でしょうか? 一般的には、型安全性や強力な記述力により生産性や信頼性が高く、パフォーマンスにも優れたソフトウェアを開発できること、と考えられているようです。しかし弊社でHaskellを採用するときに私が考えていたメリットはもう一つありました。それはHaskellを使っている企業であれば優秀なプログラマが来てくれるのではないかということです。例えば、上述のTsuru Capital様の事例紹介の記事にも以下の記述があります。
「Haskellを使う技術者には、優れた人材が多い。だからHaskellを社内の標準言語にすれば、優秀な人材を集められる」との2人の主張をCranshaw氏は受け入れ、創業以来ずっとHaskellで開発している。「HFTでは人材が肝。おかげで実際に優秀なエンジニアを採用できている」
ところが残念ながら弊社の場合には今のところこれは成立していません。今年の4月ごろにHaskellを使えるエンジニアの求人を出したところ、弊社でHaskellを使っているというイメージがなかったためか、Twitterでの反応は以下のようなものでした。
いやいやいや、本当にHaskell使っていますって!
これらのTwitterでの反応のせいではないでしょうが、今までこのポジションに応募していただいた方で面接でHaskellを使いたいと言ってきたのはたったの1名だけでした...
落ち着いてよく考えてみると、「Haskellを使う技術者には、優れた人材が多い。だからHaskellを社内の標準言語にすれば、優秀な人材を集められる」というのは、論理的には間違っています。Haskellを使う技術者に優れた人材が多いとしてもへぼプログラマもいますし、言語Aを社内の標準言語にすれば言語Aを使う技術者が採用できるとは限りません。
では、どうすればHaskellプログラマを確保できるのか? 弊社ではすでにHaskellで開発したミドルウェアを商用サービスにリリースしていますので、少なくともこれをメンテナンスするエンジニアが必要です。現在弊社には4人しかHaskellプログラマがおらず、このまま人数を増やせないようだと逆に技術的負債にもなりかねません。私が出した結論は、Haskellを地道に社内に広めていくしかないというものでした。
実はこの結論に至ったのは、CUFPで聞いてきた他社の事例の影響もあります。USではサービスを全て関数型言語で開発するというようにより突っ込んだ使い方をしているところも多いのですが、それらの事例発表の多くで強調されていたのがエバンジェリズム、そして教育の重要性です。例えば、PHPのシステムをErlangに移行したOpenXの事例でも、Erlangプログラマを採用しようとしたがほとんど採用できず、教育により人員をカバーしたとの旨が報告されていました。人材の流動性が高いUSですらそうなのですから、日本国内では社内での教育により力を入れなければいけないのは当然と言えるでしょう。
前置きが長くなりましたが、本稿では、実際に弊社でどのようなHaskell普及活動を試みているかということの例として、先日より社内で実施してきたHaskellのチュートリアルの内容と実施状況を報告したいと思います。
2. Haskellチュートリアル
2.1 チュートリアル内容の検討
弊社ではもともと、昼休みの時間にお弁当を食べながら社内での関数型言語の事例を報告する勉強会を月に1~2回実施しています。しかし他の人の事例を聞くだけでなく実際に手を動かしてコードを書いてもらわないと勉強会としては不十分だろうという意見も出ていました。そこで、昼休みの1時間を3~4回使って、実際にHaskellのコードを書くチュートリアルを実施することになりました。
ここでポイントになるのが、このチュートリアルはあくまでも自主的なものということです。NTTデータ様のように業務としてHaskellドリルができれば学習という点では効果的かもしれませんが、このチュートリアルは自主的なものなので内容が面白くなければ人が集まってくれないかもしれません。
チュートリアルの内容として当初は次のようなものが挙がっていましたが、最終的には、内容と面白さ、分量のバランスを取って3番目の簡単なWebアプリケーションを作るというものに決めました。本チュートリアルでは時間も限られていますので、Haskellを正確に理解してもらうというよりは、実際に手を動かして動くものを作ってもらい、Haskellの楽しさや実用性を体験してもらうことを目的にしました。
内容 | 学べる要素 | 長所 | 短所 | |
Haskell朝練 | 毎朝集まって「プログラミングHaskell」の課題を順番に解く | Haskell基礎 | 早起きできる 本の内容はしっかりしているので進め方を我々で考えなくていい |
昼休みではないので人が集まらないのではないか 応用はあまり紹介できない(つまらない) |
Haskellでパーサーを作ろう | Parsecのようにパーサを作れるライブラリを作成しモナドについて理解を深める | Haskell基礎、モナド | 比較的短い(プログラミングHaskell 8章、RWH 16章あたりだけでカバーできる) | 応用分野としてあまり面白くない |
Haskellでガチャを作ろう | 簡単なガチャまたはクエストのようなアプリをWebアプリフレームワークで作る | Haskell基礎、モナド、Yesod | HaskellでWebアプリが作れることを学べる | Haskellの解説はあまり詳しくはできないので駆け足になってしまう |
Haskellをブラウザで動かそう | FayでDOM操作をするプログラムを作る | Haskell基礎、Fay | ブラウザで動くので簡単に試せる | Fayモナドしか使えないなど互換性が低い ほとんど手続き型言語 |
Haskellでシェルスクリプトを書こう | Shellyでシェルスクリプトを書く | Haskell基礎、モナド、並行プログラミング | シェルスクリプトを書く機会は多いので業務で使える | 応用分野としてあまり面白くない |
Haskellで並行並列GPGPUプログラミング | Parallel and Concurrent Programming in Haskellの第6章あたりを読んでコードを書いてみる | Haskell基礎、モナド、並列プログラミング | 言葉に引かれて集まる人がいるかも | 短期間では難しい |
HaskellでWebアプリケーションを作るチュートリアルとして、Yesodなどのチュートリアルはすでに世の中にありますが、それらはHaskellの理解を前提としています。本チュートリアルではHaskellの基礎も合わせて学べるようにしなければいけません。
Haskellでガチャを作ろうの内容を具体的したものは以下の三日間のプログラムになりました。Haskellの基礎ばかり長々と解説されても飽きてしまうかもしれませんし、モナドなどの説明を先にすると挫折してしまう人もいそうなので、Haskellの基礎とYesodを使って実際に動くものを作るのをおりまぜたプログラムにしています。
1日目
- 環境設定
- Haskell Platform、Yesodのインストール
Haskellの基礎
- Hello, world! (putStrLn)
- 関数(定義と呼び出し方)
- 型、型クラス
- 構文($、リスト、タプル、if、パターンマッチ、case、let、where、オフサイドルール等)
- 繰り返し(再帰、高階関数)
- 型の定義
- 型クラスの定義
- モジュール
Yesodを使ってみる
- Hello, world! (ハンドラの追加)
2日目
- Haskellの基礎
- Hoogleの使い方
- 関数(部分適用、無名関数、関数合成)
- モナドの使い方(IOモナド、Yesodで出てくるモナド)
- アプリカティブ
Haskellの応用
- TemplateHaskellの使い方
- QuasiQuotesの使い方
YesodでBlogシステムを作ってみよう
- Scaffoldの構造
- テンプレート
- フォーム
3日目
- Haskellの基礎
- モナド変換子
- FacebookT
YesodでBlogシステムを作ってみよう
- DBの読み書き(Persistent)
- JSON(Aeson)
- 認証と認可(Yesod.Auth)
...はい。これだけの内容を3時間でできるはずがありませんね(;^_^A)。実際にやってみるとやはり3回では終わらず、全部で5回かかりました。
もちろん普通にやれば5時間かけてもこれだけの内容をやるのは難しいでしょう。本チュートリアルでは時間が限られていますので、モナドの定義などの説明ははしょっています。例えばモナドに関しては、Monad型クラスのインスタンスであれば以下のように型をそろえればdo記法の中で並べることができ、IO、Maybe、HandlerなどがMonad型クラスのインスタンスであることを説明するだけにとどめています。
OK:
1 2 3 4 5 |
main :: IO () main = do print "What is your name?" :: IO () name <- getLine :: IO String print ("Hello " ++ name ++ "!") :: IO () |
NG:
1 2 3 |
getSmilingLine = do line <- getLine :: IO String line ++ " (^_^)/" :: String -- IOで始まっていないのでエラーになる |
アプリカティブは若干応用的な内容と思われますが、Yesodのフォームがアプリカティブスタイルで使うことが前提になっているためプログラムに入れることにしました。Template Haskell、準クォートについてもYesodのテンプレートで使われているため説明に追加しています。
実際にチュートリアルに使用したテキストはこちらに公開しています。https://github.com/gree/haskell-tutorial/wiki
2.2 チュートリアル実施結果
社内のチャットや勉強会を通じてこのチュートリアルをアナウンスした結果、初日には会議室の定員一杯の24名が集まりました。かなり駆け足のチュートリアルでしたので途中で挫折する人が多いのではないかと予想していたのですが、意外にもかなりの人が4日目まで集まりました。
人数 | 内容 | |
1回目 | 24人 | Haskellの基礎(繰り返しまで) Yesodを使ってみる |
2回目 | 21人 | Haskellの基礎(型、型クラス、モジュール) YesodでBlogシステムを作ってみよう(テンプレート) |
3回目 | 21人 | Haskellの基礎(Hoogle、関数、モナド) YesodでBlogシステムを作ってみよう(テンプレート) |
4回目 | 22人 | Haskellの基礎(アプリカティブ、Template Haskell、準クォート) YesodでBlogシステムを作ってみよう(フォーム) |
5回目 | 11人 | Haskellの基礎(モナド変換子、FacebookT) YesodでBlogシステムを作ってみよう(Persistent、Aeson、Yesod.Auth) |
※参加者は全て同じ人ではなく、途中から参加した人も数名いる。
実施にあたって問題になったのがYesodのインストールでつまづいてしまう人が多かったことです。本チュートリアルでは、Haskell Platformをインストールした後にcabal install yesodでインストールする手順にしていたのですが、社内で開発環境として使われてるLinuxのVMのメモリが1GBしかないためにGHCがセグメンテーションフォルトで停止してしまったり、MacのOSをMavericksにアップデートしたことにより問題が起きていたようです。これを解決するために、インストールがうまくできない人向けにYesodインストール済みのVagrant boxを作成し提供しました。
また、途中で設けた練習問題でつまづく原因の多くは、サンプルプログラムが間違っていてコンパイルできない(TextがStringになっていたなどのミスがあった)などの教材の不備によるところが多く、モナドが理解できない等の理由ではありませんでした。
有効回答数が11人と少ないので参考程度ですが、チュートリアル終了後に5段階評価で理解度のアンケートを取ったところ以下の結果になりました。グラフは平均を取ったものです。
モナド変換子やTemplate Haskell等の応用的な項目に関してはさすがにきびしい結果になりましたが、モナドに関しては半数以上の参加者が5段階評価で3以上を回答しており、本チュートリアルでは大きな壁にはなっていなかったようです。
2.3 考察、反省など
まず、Yesodのインストールができないなどの不手際があったにも関わらず、多くのエンジニアが業務時間外に参加し続けました。Haskellを学びたいと思っている人は多く、こうしたチュートリアルの需要は高いのではないかと考えられます。
Yesodの環境設定に関しては、最近ではFPCompleteのようなサービスもありますのでこれを使うということも考えられました。しかし、GHCのバージョンが7.4と古かったり、scaffoldの内容が通常のyesodコマンドで生成したものと異なっていたりと、あとあと自分でいじるときに応用が効かなくなるのではないかと考えてやめました。この辺が妥協できるのであればFPCompleteを使うのが手軽でよいかもしれません。
また、型クラス、モナド等の概念はHaskellを学習する際につまづくポイントではないかと予想していましたが、理解度アンケートの結果や、3回目、4回目の参加者数を見る限りこれらのポイントで挫折してしまった人は少ないようです。概念を説明するのではなく、本チュートリアルのように使い方のみに的をしぼって説明すれば短時間でもかなりの人が理解できるのではないかと考えられます。逆に、4回目はアプリカティブ、Template Haskellなど内容を詰め込みすぎたのと、フォームのサンプルが前述のように間違っていてコンパイルできないという問題があり、5回目の参加者の大幅減につながってしまいました。
チュートリアルのサンプルはきちんとコンパイルしてチェックすべきであったのですが、教材を社内のGitHubのWikiにコピーする際にミスが生じてしまったようです。現状literate HaskellではTeX形式か行頭に">"を入れる形式しかサポートされていませんが、行頭に4つスペースを入れるMarkdownの形式もサポートしてくれればこうしたミスを減らせたかもしれません。
2.4 Yesodに関するTips
細かい話になりますが、チュートリアル用の環境設定をする際に、普段私が使っているEmacs+ghc-modの環境とYesodの相性があまりよくないということに気がつきました。一点目はハンドラのチェックができないというのと、二点目はflymakeで生成されたファイルをYesodの開発サーバがいちいちビルドしてしまうということです。
ハンドラのチェックができないというのは、ハンドラからインポートされているFoundation.hsでTemplate Haskellを使って設定ファイルをインポートしているのですが、プロジェクトのルートディレクトリでコンパイルされることを想定しているので、ハンドラのディレクトリでコンパイルすると設定ファイルを見つけられず Exception when trying to run compile-time code エラーになるというものです。これは次のような簡単なシェルスクリプトを書けば回避することができます。
1 2 3 4 5 6 7 8 9 10 11 12 |
#!/bin/sh # hack for Yesod pwd=`pwd` dir=`basename $pwd` if [ $dir == "Handler" ] ; then cd .. fi ghc_mod $@ |
Emacsからはこれを呼び出すように.emacsにghc-module-commandを設定してやれば大丈夫です。
1 |
(setq ghc-module-command "~/bin/yesod-ghc-mod.sh") |
もう一点はflymakeで生成される ほげほげ_flymake.hs というファイルをYesodの開発サーバがファイル変更と勘違いしていちいちビルドしてしまうということです。常にコンパイルが走りCPU使用率が高くなる以外は問題はないのですが、ノートPCだとファンがうるさいですし、バッテリー消費も大きくなってしまいます。
私が調べた範囲では、Yesodの起動オプション等ではこの振る舞いは制御できないようなので、私はyesod-binのwatchForChanges関数を以下のように変更してしまいました。
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 26 27 28 29 30 |
<ul> <li> yesod-bin-1.2.3.3/Devel.hs 2013-12-04 23:47:25.861879300 +0900</li> <li> yesod-bin-1.2.3.3.new/Devel.hs 2013-11-24 21:50:05.944134100 +0900</li> </ul> @@ -345,7 +345,9 @@ watchForChanges :: MVar () -> [FilePath] -> [FilePath] -> FileList -> Int -> IO (Bool, FileList) watchForChanges filesModified hsSourceDirs extraFiles list t = do newList <- getFileList hsSourceDirs extraFiles <ul> <li> if list /= newList</li> <li> let list' = Map.filterWithKey isNotTemp list</li> <li> newList' = Map.filterWithKey isNotTemp newList</li> <li> if list' /= newList'</li> </ul> then do let haskellFileChanged = not $ Map.null $ Map.filterWithKey isHaskell $ Map.differenceWith compareTimes newList list `Map.union` @@ -359,6 +361,7 @@ | otherwise = Just x isHaskell filename _ = takeExtension filename `elem` [".hs", ".lhs", ".hsc", ".cabal"] <ol> <li> isNotTemp filename _ = not $ "_flymake" `L.isInfixOf` filename</li> </ol> checkDevelFile :: IO () checkDevelFile = do |
これでも.hsファイル変更時にはghc-modとYesodの開発サーバが両方とも動くのでかなりのCPU使用率になりますが、多少は改善されます。
3. まとめ
Haskellを採用する企業は業界や規模を問わず今後もますます増えていくでしょう。弊社でのチュートリアルの参加者数を見てもわかるように、Haskellを学びたい、理解したいと思っているエンジニアも以前にもまして多くなっているようです。
業務ではなく個人の時間でHaskellを書いているHaskellプログラマの方も、一度社内でチュートリアルを開いてみてはいかがでしょうか。意外にも多くの人が集まり、次のプロジェクトではHaskellを採用というようなことになるかもしれません。弊社でも今後もHaskellの普及に力を入れていきます(^_^)/
明日は池原さんです。弊社でも随一のHaskellプログラマなので面白い記事になると期待しています!