読者です 読者をやめる 読者になる 読者になる

OS X Yosemite における LSOpenURLsWithRole エラーの解決

これまで自分はアプリケーションを起動したりファイルを開いたりするのにopenコマンドを多用していたのですが、OS X Yosemiteにアップグレードしてから

LSOpenURLsWithRole() failed for the application /Applications/TextEdit.app with error -10810 for the file /some/file

というエラーが起こって困っていました。

このエラーはTmux上でopenコマンドを使用した場合にのみ起こり、通常では問題なく使えていました。

openコマンドじゃなくてアプリケーションなどでこのエラーが出ている人でも、tmux上で起動した時だけ上記エラーが出るようなら下の解決策が有効かもしれません。

ReattachToUserNamespaceのインストール

結論から言うと、ReattachToUserNamespaceというアプリケーションをインストールして、Tmuxのウィンドウ作成時にはそのコマンドを噛ませたシェルを起動するように変更すると問題がなくなりました。

恥ずかしながら自分は全く知らなかったのですが、このアプリケーションはTmux上でコピーしたものをMac OS Xクリップボードにもコピーするための結構有名なものだそうです。

だからあんまりこのエラーで詰まっている人が見つからなかったのかな...

インストールはHomebrewでできます。とてもべんり。

$ brew install reattach-to-user-namespace

.tmux.confの変更

Tmuxのウィンドウ作成時に起動するアプリケーションは.tmux.conf内でdefault-commandオプションを設定することで変更できます。

set-option -g default-command "which reattach-to-user-namespace > /dev/null && reattach-to-user-namespace -l $SHELL || $SHELL"

default-commandオプションはデフォルトでは空になっており、からの場合はログインシェルがデフォルトコマンドになります。

今回はreattach-to-user-namespaceコマンドがない場合でも正常に起動できるよう、whichコマンドの結果によってシェルの起動にReattachToUserNamespaceを噛ませるかどうかを変更するようにしました。

.tmux.confの変更が反映されない場合

ずっと前にもこれでハマってすっかり忘れていたのですが、.tmux.confの変更を反映させるためにはTmuxのプロセスを一度全て終了させる必要があるため、デタッチしたセッションがある場合には一度すべて殺さなければいけません。

Tmuxセッションが残っているかどうかはpsコマンドで確認します。

$ ps aux | grep taux

自分は以上でopenコマンド使用時にエラーが起こることはなくなりました。 ついでに Mac OS X の tmux でクリップボードを使えるようにした(pbcopyとか) - 戦場のプログラマー とか tmuxとMacのクリップボードを共有する(copy-mode, vim) - Qiita を見てクリップボード共有の設定もして、今では穏やかにTmuxを使いまくっています。

Mac OS XのためのHaskell開発環境

はじめに

2012年ごろにとてもHaskellが流行り、たくさんの入門記事が書かれたものの、最近は関数型言語の物珍しさもあまりなくなり、むしろScalaやらC++11やらPythonの形に関数型の手法が定着しつつあるように感じます。

そんな中で出されたO'REILLY本「Haskellによる並列・並行プログラミング」を読み始めたわけですが、サンプルを動かそうとすると、これまでcabal installでホイホイしてきたせいで多くのパッケージの依存関係が壊れていてイライラする。

というわけでHaskellの恐らく今最もスタンダードな環境構築を一からやり直したので、知っておいた方がいい情報と合わせてメモしてみました。 OSはMac OS X Mavericks (10.9.4)です。

GHC・Cabalのインストール

Haskellコンパイラの定番GHCとパッケージ管理ツールCabalのインストールです。Mac OSなので素直にHomebrewでインストールしました。Linux環境でもapptitudeやyumで簡単にインストールできるはず。

$ brew install ghc cabal-install

結構時間がかかるかと思いますが、特にエラーなくインストール出来ました。 なにか怒られてもエラーを読んだりbrew doctorしてみたりすればすぐに出来るかと思います。

ちなみにインストールできたバージョンは + GHC version 7.8.3 + Cabal library 1.20.0.2 + cabal-install 1.20.0.3 でした。

また、インストールしたcabalコマンドを使うために.bash_profileなどお使いのシェルの起動時スクリプト$HOME/.cabal/bin環境変数PATHに追加しておくと楽でしょう。

GHCの使い方

GHCghcコマンドでソースコードコンパイルが出来る他、ghciコマンドで対話的な実行環境として実行できます。 主にghciコマンドは後述するcabal repl越しに関数の動き方の確認などに使用することとなるでしょう。

また、ghcコマンドにはGCCと同じように多彩なオプションが用意されています。 詳細はGHCのマニュアルを見ればわかりますが、以下に便利なオプションをいくつか書いておきます。

-o

$ ghc -o hoge Hoge.hs

基本ですが、一応。出力する実行ファイルの名前を指定します。 指定しない場合の出力ファイル名はmain関数を含むソースファイルのサフィックスを除いたものとなります。

-On

最適化オプションです。0~2の3段階があり、0は最適化なし、2は可能な限り最適化。指定しない場合は1となります。

-S

$ ghc -S Hoge.hs

...お楽しみオプションです。 ソースコードコンパイルアセンブルする前の状態(=アセンブリファイルを生成した状態)で止めます。 これによってGHCがどんなアセンブリを生成しているのか知ることができますが、ちょっとしたコードでもかなりの行数となるため、アセンブリコードからプログラムの性能を予測する、というのは現実的ではないかと思います。

ちなみに、GCCと同じように-cオプションでリンク前までコンパイルすることもできますが、GHCでは渡されたソースコードにmain関数が含まれていなかった場合は自動的にオブジェクトの生成まででストップするため、断片コードのコンパイル時に明示的に-cを付ける必要はありません。

cabalの使い方

Cabalを使うとRubyのBundlerのようにパッケージを管理することができます。 以下によく使用するコマンドを列挙しておきます。 なお、各コマンドのさらなる詳細は

$ cabal <コマンド名> --help

とするとみることができます。

init

$ cabal init

とすると対話的に自分の作成するプロジェクトのための.cabalファイルを作成することができます。

configure, build

$ cabal configure
$ cabal build

とするとカレントディレクトリのプロジェクトを.cabalファイルに基づいてビルドすることができます。 Makefileよりも最適化された形があるのはとても嬉しいですね。

update

$ cabal update

とすると最新のパッケージリストを取得できます。パッケージのインストールを行う前に実行します。

list

$ cabal list <文字列>

とするとbrew searchのように<文字列>にマッチするcabalでインストール可能なパッケージの一覧が取得できます。

info

$ cabal info <パッケージ名>

とするとbrew infoのように<パッケージ名>で指定したパッケージの詳細を表示します。

sandbox

$ cabal sandbox init

とするとbundle install--pathオプションのようにカレントディレクトリ以下でパッケージを個別に管理します(かつては同様の目的でcabal-devパッケージが使用されていました)。

sandboxを指定せずにcabal installをしてしまうと、プロジェクトごとに使用するパッケージのバージョンを管理できなくなるため、ひどいことになります。

パッケージのインストール前には必ずプロジェクト用のディレクトリを作成し、cabal sandbox initを叩くようにしましょう。

install

$ cabal install <パッケージ名>

とすると<パッケージ名>で指定したパッケージをインストールします。 前述のsandboxを使用していると、sandboxで指定されたディレクトリ(デフォルトでは./.cabal-sandbox)以下にパッケージがインストールされます。

-jnなどとすると-jnnで指定した数のジョブを同時に実行してくれるため、コアをフルに使ってインストールすることができます。

exec

$ cabal exec <コマンド名> -- <オプション>

とすると<コマンド名>で指定したsandbox内にインストールしたパッケージの実行ファイル(デフォルトでは./cabal-sandbox/bin以下にあるもの)を<オプション>付きで実行できます。

これもRubyのBundlerと似た機能ですね。

repl

$ cabal repl

とすると、カレントディレクトリのsandboxにインストールしたパッケージと.cabalファイルで指定されているmain関数のあるファイルをロードしたghciを起動することができます。

これはパッケージで提供されている関数などの挙動を確認する上で一番手っ取り早く、便利な方法になります。

ghc-pkgの使い方

ghc-pkgコマンドでインストール済みのパッケージに対する操作を行うことができます。 こちらも以下によく使うものを列挙しておきます。

list

$ ghc-pkg list

とすると、グローバルスコープのインストール済みパッケージを一覧できます。 この時、ghcのインストール場所のpackage.conf.d$HOME/.cabal以下のpackage.conf.dの2つに分かれてパッケージリストが表示されますが、前者がシステム用のパッケージ、後者がユーザのインストールしたパッケージのようです。

なお、Cabalを用いて

$ cabal exec ghc-pkg list

とするとsandboxにインストールしたパッケージも一覧することができます。

check

$ ghc-pkg check

とすると、依存関係の壊れたパッケージを一覧することができます。

field

$ ghc-pkg field <パッケージ名> <フィールド名>

とすると、<パッケージ名>に指定したパッケージの.confファイルの<フィールド名>に指定したフィールドの値を表示できます。

これを用いて

$ ghc-pkg field <パッケージ名> exposed-modules

とすれば、指定したパッケージで提供されるモジュールの一覧を表示できます。

unregister

$ ghc-pkg unregister <パッケージ名>

とすると、指定したパッケージをアンインストールすることができます。

おわりに

これらを知っておくだけで、かなりHaskellでの開発が楽しくなるかと思います。

というかたのしい。

参考サイト

Haskellのパッケージ管理について調べてみた - りんごがでている

2013年8月現在のHaskell開発環境 - maoeのブログ

HaskellのYesodでWebアプリ開発入門 (4)

はじめに

HaskellでYesodを使ってWebアプリケーションを作ってみている際のメモ記事です。

前回はビューを構成するためのウィジェットについて学び、CSSライブラリのバージョンアップを行いました。

Hamletを用いたレイアウト

今回はいよいよ、Hamletファイルを編集していきます。 なお、templates/layout/wrapper.hamletのほとんどを占めているIE対策コードやGoogle Analyticsのコードについてはここでは触れません。 サイトの用途に依っては重要ではありますが、それぞれの環境によって自分の思うように整形したりIE対策をどこまでやるか考えればいいかと思います(ここのサンプルコードではIE9以降のブラウザのみへの対応を考えています)。

さっそく、templates/layout/application.hamletからはサンプルコードのために書かれていたコードを取り除いて標準的なHTML5のテンプレート的な内容に書き換え、templates/layout/wrapper.hamletからは不要なコードを削除して単純化を行いました。

なお、ナビゲーションバーの内容がHomeとContactだけでは寂しいので、2回目で行なったのと同じ手順でAboutとHelpのハンドラとビューも追加しました。 この追加の仕方は、完全にrailstutorial.orgのパクリですね。

templates/layout/application.hamlet

$newline never
<div #main>
    <header>
        <h1>
            <a href=@{HomeR}>Sample App
        <nav>
            <ul>
                <li>
                    <a href=@{AboutR}>About
                <li>
                    <a href=@{HelpR}>Help
                <li>
                    <a href=@{ContactR}>Contact

    ^{widget}

    <footer>
        <p>&copy; 2014 demmy_s

templates/layout/wrapper.hamlet

$newline never
$doctype 5
<html lang="en">
    <head>
        <meta charset="UTF-8">

        <title>#{pageTitle pc} | Yesod Sample
        <meta name="description" content="Sample Application of Yesod">

        <meta name="viewport" content="width=device-width, initial-scale=1.0, minimum-scale=1.0, maximum-scale=1.0, user-scalable=no">

        ^{pageHead pc}
        $maybe analytics <- extraAnalytics $ appExtra $ settings master
            <script>
              if(!window.location.href.match(/localhost/)){
                window._gaq = [['_setAccount','#{analytics}'],['_trackPageview'],['_trackPageLoadTime']];
                (function() {
                \  var ga = document.createElement('script'); ga.type = 'text/javascript'; ga.async = true;
                \  ga.src = ('https:' == document.location.protocol ? 'https://ssl' : 'http://www') + '.google-analytics.com/ga.js';
                \  var s = document.getElementsByTagName('script')[0]; s.parentNode.insertBefore(ga, s);
                })();
              }

        \<!--[if lt IE 9]>
        \<script src="http://html5shiv.googlecode.com/svn/trunk/html5.js"></script>
        \<![endif]-->
    <body>
        ^{pageBody pc}

とりあえず編集してページを見ると、嬉しい事にしっかりとルーティングがなされた汚い(CSSのない)ページを楽しむことができます。

なお、FOOTERタグ内のコピーライトについて、Scaffoldで生成されたものではconfig/settings.ymlで記述したものを表示するようになっていたのですが、通常それほど書き換える需要もないかと思いますので、単にベタ書きに変更しました。

不要になったextraCopyrightなどが気になる場合はSettings.hsの68行目以降を以下のように編集し、config/settings.ymlからcopyrightの行を取り除いてしまえばいいでしょう。

data Extra = Extra
    { extraAnalytics :: Maybe Text -- ^ Google Analytics
    } deriving Show

parseExtra :: DefaultEnv -> Object -> Parser Extra
parseExtra _ o = Extra
    <$> o .:? "analytics"

また、Foundation.hsdefaultLayout内にあったmmsg <- getMessageという記述もサンプルアプリケーション用の記述を消したので必要無くなり、コンパイル時にWarningとして出てしまうので、消しておくといいでしょう。

サンプルの方では前回Hamletの構文として紹介しなかったものが続々と出てきましたので、順に説明しておこうかと思います。

糖衣構文

まず、それぞれの1行目とwrappter.hamletの2行目はただの糖衣構文です。 $newline neverとすると、最終的に生成するHTMLから改行とインデントを除き、ファイルサイズを小さくしてくれるので、基本的には書いておくといいかと思います。 $doctype 5は容易に想像がつく通り、HTML5のDOCTYPE宣言を挿入してくれるだけの構文です。

なお、DOCTYPE宣言は古いYesodのバージョンでは!!!と書くだけで挿入されていましたが、わかりづらいために現在では推奨されていません。

また、BODYタグ直下のDIVタグで用いている#mainという表記はid="main"の糖衣構文です。Hamletでは同じようにクラスの指定も.mainのように行うことができます。

エスケープ記号

Hamletテンプレートファイル内でスペース以外を含まない行頭に\を記述した場合にはその行はエスケープされ、\の後からスペースを含まない行末までに記述した内容がそのまま最終的なHTMLに含められます。

また、上記のサンプルでは使用されていませんが、行末に#を記述することで、その#以前に記述されたスペースをエスケープされたとしてそのまま最終的なHTMLに含ませることができます。

これらの含まれたコードは少し複雑にも思えますが、コメントや他のサービスから提供される埋め込み用コードをシンプルな記述で含ませられるのは選択肢として悪くない気がします。

内挿

続いて、全体に散りばめられている#^@で始まり{ }が続いている構文は内挿(Interpolation)です。これまでにも少し触れましたが、Haskellのコードを型安全に埋め込むための構文であり、{ }の前の記号は埋め込まれる値の型を特定しています。

以下に記号と内挿できる値の対応関係を書いておきます。

記号 内挿できる型
# ToHtmlクラスのインスタンスとなっている型
^ Hamletテンプレートを表す型
@ config/routesなどで作成されたURLの型

なお、@{ }で内挿されるURLの文字列はドメイン名までを含む完全なURLとなります。

ひとまずHamletの説明は以上です。 次は、CSSを出力するテンプレートを用いてナビゲーションバーとフッターのデザインをしてみます。

Luciusを用いたデザイン

LuciusはCSSの拡張としてYesodで用意されているCSSテンプレートエンジンの1つですが、構文はほとんどRoRでのSassやLessと同じようなものとなっていますので、特にとっつきにくさもなく導入できるかと思います。

今回もHamletの時と同様にまずサンプルプログラムのために書いたLuciusファイルを紹介したいと思います。

Foundation.hsの中でのtemplates/layout/application.hamletの展開は2回目の記事で紹介したwidgetFile関数を使用して行われているので、これを利用するために、編集するLuciusファイルはtemplates/layout/application.luciusという名前で保存します。

/*
 * Global
 */
@defaultColor: #222;
@inverseColor: #fff;

html {
    color: #{defaultColor};
    font-size: 100%;
    height: 100%;
}

body {
    height: 100%;
}

h1,p,ul {
    margin: 0;
}

h1 {
    font-weight: normal;
}

a {
    color: #{defaultColor};
    text-decoration: none;
}

ul {
    list-style: none;
    padding-left: 0;
}



/*
 * Layout
 */
@sidePadding: 3%;

@headerBackColor: #222;

@buttonColor: #bbb;
@buttonActiveColor: #fff;

@footerLineColor: #ccc;

#main {
    width: 100%;
    margin: 0 auto;
    padding-bottom: 3em;
}

#main>header {
    display: table;
    width: 100%;
    padding: 0.5em 0;
    background-color: #{headerBackColor};

    >* {
        display: table-cell;
        vertical-align: middle;
    }

    >h1 {
        font-size: 1.3em;
        width: 10em;
        padding-left: #{sidePadding};

        >a {
            color: #{buttonActiveColor};
        }
    }

    >nav {
        padding-right: #{sidePadding};

        >ul {
            display: table;
            table-layout: fixed;
            float: right;

            >li {
                display: table-cell;
                font-size: 1em;
                vertical-align: middle;
                text-align: center;
                width: 5em;

                >a {
                    color: #{buttonColor};
                }
                >a:hover {
                    color: #{buttonActiveColor};
                }
            }

            >li.active>a {
                color: #{buttonActiveColor};
            }
        }
    }
}

#main>section {
    margin: #{sidePadding};
}

#main>footer {
    text-align: right;
    padding: 0.7em 1.5% 0 1.5%;
    margin: 3em 1.5% 0 1.5%;
    border-top: 1px solid #{footerLineColor};
}



/*
 * Components
 */
@normalBackColor: #ccc;
@redBackColor: #c0392b;
@greenBackColor: #27ae60;
@blueBackColor: #2980b9;

.red {
    color: #{inverseColor};
    background-color: #{redBackColor} !important;
}
.green {
    color: #{inverseColor};
    background-color: #{greenBackColor} !important;
}
.blue {
    color: #{inverseColor};
    background-color: #{blueBackColor} !important;
}

.button {
    display: inline-block;
    padding: 0.7em 1em;
    border-radius: 5px;
    background-color: #{normalBackColor};
}

Lucius内ではHamletと同様に#{valiable}で変数を展開できるだけでなく、@valiable: value;という構文によって変数を定義することができます。もちろん、url( )による指定では@{ }という形の内挿を記述することで型安全なURLを埋め込むことができます。

また、SassやLessと同じように{ }を入れ子にすることで、子要素へのスタイルの適用を記述することができます。

なお、LuciusMixinテンプレートを利用することでミックスインを簡単に記述することもできるのですが、それについてはまた出てきた際に説明します。

このLuciusファイルの適用でナビゲーションバーとフッターがいい感じにデザインされましたが、まだトップページがHello, Wold!だけの悲しい状態ですので、またまたrailstutorial.orgのデザインを真似てtemplates/home/index.hamlettemplates/home/index.luciusも編集してみました。

<section>
    <h1>Welcome to the Yesod Sample
    <p>This is the home page for the Yesod sample application.
    <a href="#" .button .blue>Sign up now!
@topBackColor: #eee;

#main>section {
    background-color: #{topBackColor};
    text-align: center;
    padding: 3em;
    border-radius: 10px;

    >h1 {
        font-weight: bold;
        font-size: 2.5em;
        margin-bottom: 0.2em;
    }

    >p {
        font-size: 1.2em;
    }

    >a {
        margin-top: 1em;
    }
}

おもいっきりパクリではありますが、なかなか良い感じになりました。

f:id:demmy_s:20140522113858p:plain

それでは、デザインでモチベーションを保ちつつ、残りの構文についても説明してしまいたいと思います。

Cassiusの構文

CassiusはLuciusの構文をよりHaskellらしくしたもので、スコープを表現するために{ }ではなくインデントを用い、行の終りを表現するために;ではなく改行を用います。 他の部分はLuciusと全く同様になっており、実際内部的にもLuciusと同じパーサが用いられているだけです。

原理主義的な発想ではCassiusを使用するべきなのかもしれませんが、Luciusの方が既存のCSSもそのまま一緒に記述できるため、ここでは一貫してLuciusのみを使用していきます。

Juliusの構文

Juliusは単純に、Hamletと同様の内挿を行えるようにしたJavaScriptの拡張です。 URLの記述や変数によるハンドラからの値の受け渡しができるというだけですが、本格的なアプリケーションを構築していく上では相当助けとなる機能でしょう。

おわりに

今回はYesodのフロントエンドを支えるShakespearen TemplateのDSL構文を順に見ていきました。

これで、とりあえず静的なWebサイトであれば自由に作れるくらいにはYesodがわかってきたかと思います。

次の内容についてはまだ考えていませんが、そろそろ本格的にYesodアプリケーションで遊ぶための環境をつくりたいので、本番環境の用意と結合テストの書き方などを見ていこうかと考えています。

HaskellのYesodでWebアプリ開発入門 (3)

前回の記事

HaskellでYesodを使ってWebアプリケーションを作ってみている際のメモ記事です。

ずいぶんと間が開いてしまいましたが、前回はディレクトリ構造のチェックとトップページ・コンタクトページへのルートの追加を行いました。

ウィジェット

今回は前回作ったトップページとコンタクトページをそれなりの見た目に整形していこうと思います。 ただ、DSLの構文を見る前に、Yesodでのビューの扱い方について少し知っておく必要がありました。 Yesodでは他のフレームワークとは少し違う、ウィジェットと呼ばれる概念を使ってページの構成を行なっています。

ウィジェットはページテンプレートで扱うコンテンツの柔軟性を向上し、テンプレートファイルを複数に分割したり、例外的なビューを作るたびにテンプレートファイル内に分岐を追加するようなことを防いでくれる、シンプルですが非常に有用な概念です。

ウィジェットではページのコンテンツを以下の7種類に分類して保持します。

Hamletなどで作成したページコンテンツはtoWidget関数を用いるだけでウィジェットに変換することができ、その際にちゃんとCassiusやLuciusのデータはSTYLEタグで囲まれ、JuliusのデータはSCRIPTタグで囲まれます。

toWidget関数ではデフォルトで、CassiusやLuciusのデータはHEADタグ内のコンテンツ、HamletやJuliusのデータはBODYタグ内のコンテンツとしてウィジェットに変換しますが、代わりに以下の関数を用いることで、変換する種類を特定できます。

関数 変換する種類
setTitle ページタイトル
addStylesheetRemote 外部スタイルシート
addScriptRemote 外部JavaScript
addStylesheet スタイルシートファイル
addScript JavaScriptファイル
toWidgetHead HEADタグ内のコンテンツ
toWidgetBody Bodyタグ内のコンテンツ

ちなみに、「外部〜」と「〜ファイル」は、指定するURLが外部の絶対URLであるかプロジェクト内のルートであるか、というだけの違いです。

それで、これをどう使うか、ですが、ここで前回さらっと流してしまったdefaultLayout関数もう一度見てみます。 (コメントを省略しています)

    defaultLayout widget = do
        master <- getYesod
        mmsg <- getMessage

        pc <- widgetToPageContent $ do
            $(combineStylesheets 'StaticR
                [ css_normalize_css
                , css_bootstrap_css
                ])
            $(widgetFile "default-layout")
        withUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

なお、この中のwidgetToPageContentは大体以下の様な定義の関数です。

widgetToPageContent :: Widget -> Handler (PageContent url)
data PageContent url = PageContent
    { pageTitle :: Html
    , pageHead :: HtmlUrl url
    , pageBody :: HtmlUrl url
    }

(Yesod Web Frameword Book - Using Widgetsより引用)

そして、長過ぎるので貼り付けることはしませんが、templates/default-layout-wrapper.hamletでは<title>#{pageTitle pc}, ^{pageHead pc}, ^{pageBody pc}という行があります。

つまり、defaultLayout関数はwidgetToPageContent関数に全てのウィジェットを生成するdo構文の式を適用して、ページのコンテンツとして埋め込める形にウィジェット達を整形してpcという変数へ束縛し、その整形結果を埋め込むよう目論んだtemplates/default-layout-wrapper.hamletを変数pcのスコープ内でwithUrlRenderer関数に適用することで、その式が評価された時にはバラバラに追加されたウィジェット達がレイアウトの中に整然と並ぶよう仕向けているのです。

まだ上の7つの関数の使い所はわかりづらいので、defaultLayout関数の呼び出し元も確認してみます。 前回書いたHandler/Home.hsと、補足としてtemplates/default-layout.hamletも以下に貼り付けました。

{-# LANGUAGE OverloadedStrings #-}
module Handler.Home where

import Import

getHomeR :: Handler Html
getHomeR = defaultLayout $(widgetFile "home/index")
$maybe msg <- mmsg
    <div #message>#{msg}
^{widget}

defaultLayout関数に適用されている$(widgetFile "home/index")という式はdefaultLayout関数内でwidgetという変数に束縛され、それがdefaultLayout関数内のdo構文の中で展開されているtemplates/default-layout.hamletの中に埋め込まれています。

つまり、defaultLayout関数の引数として先の関数達をつなげたdo構文を渡せば、それが最終的なHTMLにもうまく反映される、ということです。実際にHandler/Home.hsHandler/Contact.hsを編集してみます。

{-# LANGUAGE OverloadedStrings #-}
module Handler.Home where

import Import

getHomeR :: Handler Html
getHomeR = defaultLayout $ do
    setTitle "Home | Yesod Sample"
    $(widgetFile "home/index")
{-# LANGUAGE OverloadedStrings #-}
module Handler.Contact where

import Import

getContactR :: Handler Html
getContactR = defaultLayout $ do
    setTitle "Contact | Yesod Sample"
    $(widgetFile "contact/index")

ちゃんとタイトルが付いたかと思います。

もちろんこれらのことはRoRなどでもできないことはありませんが、遅延評価とモナドの力がなければこれほど自然には書けず、多くの暗黙的ルールをドキュメントで学ばないと使いこなせない代物になってしまっていたのではないでしょうか。

Hamletの構文

YesodではHTMLのテンプレート言語としてHamletを用いているので、まずはその構文をについて知る必要があります。 細かいことはYesod Web Framework BookのShakespearean Templatesの項に書いありますが、Hamletの特徴としては大体以下のような点があるように思いました。

  1. インデントでタグのスコープを見るために閉じタグが要らない
  2. 型安全にHaskellの値を埋め込むことができる
  3. 呼び出されたスコープを引き継ぐ(引数で渡したりすることなく、束縛されている値や関数を参照できる)
  4. 手続き的なディレクティブで分岐やループ、パターンマッチを扱える

1つめはそのままですが、2つめの埋め込める値は文字列や数字だけでなく、ToHtmlクラスのインスタンスとなっている全ての型の値となります。 ToHtmlクラスの型はtoHtml関数とfromHtml関数でHtml型と行き来できる必要があります。

また、3つめはこれまで見てきたコードからも分かる通り、テンプレートファイル内のスコープはそのテンプレートが展開される関数のスコープに属することを表します。 RoRでは呼び出したコントローラを表現するクラスがスコープとなっていたので、クラス変数の汚染があり、特定のアクションのための変数を他のアクションから分離できませんでした。 それを、そもそもHaskellではそういったスコープが存在しないので、普通にしていると引数を使う必要が出てきますが、TemplateHaskellでHamletファイルを評価する関数を呼び出したスコープのクロージャにすることでいいとこ取りをした、ということですかね。

4つめが非常に煩雑なのですが、Hamletでは分岐やループ、パターンマッチといった構文を必要とする動的なコンテンツの挿入に$記号から始まる特殊なディレクティブを用います。詳細についてはYesod Web Framework BookのHamlet Syntaxの項に網羅されているので、ここでは今後出てきた都度に紹介する形にしようかと思います。

また、HamletではHTMLをそのまま書くよりもIDやクラス、その他の属性値を簡単に設定できるようになっていますが、これらも今後出てきた際に都度紹介します。

レイアウトファイルの移動

さて、説明が長くなってしまいましたが、Hamletを用いたコーディングの方法がだいたいわかったので、主にレイアウトファイルをいじって、前回作成したトップページとコンタクトページをまあまあまともなページにしてみたいと思います。

まず、ディレクトリ階層が浅いと後々不便になるかと思うので、レイアウト用のHamletファイルをlayoutディレクトリ以下に移動し、名前も分かりやすいものに変更します。

$ cd templates
$ mkdir layout
$ mv default-layout.hamlet layout/application.hamlet
$ mv default-layout-wrapper.hamlet layout/wrapper.hamlet

この変更にともなって、Foundation.hsの中のdefaultLayout関数で指定しているファイル名を変更する必要があります。

        pc <- widgetToPageContent $ do
            $(combineStylesheets 'StaticR
                [ css_normalize_css
                , css_bootstrap_css
                ])
            $(widgetFile "layout/application")
        giveUrlRenderer $(hamletFile "templates/layout/wrapper.hamlet")

ところで、ファイルを編集するとyesod develのプロセスがカタカタと動いてリビルドした上でエラーがあったら提示してくれるというのは、RoRでGuardを使ってテスト駆動開発をしている時と同じくらいの快適さと安心感ですね。 まだテストコードは1行も書いていないというのに...。こんなんだったら、ユニットテストなどほとんど必要なくなるように思えます。

Normalize.cssのバージョンアップとBootstrap.jsの不要な箇所の削除

次に、ずっと放置していましたがこの際なので、Normalize.cssを最新版にバージョンアップします。ついでにBootstrap.jsもバージョンアップしようかと思いましたが、個人的にこれが入るとCSSが格段に書きづらくなるため、ひとまずBootstrap.jsは便利なGlyphicon部分だけ残して、後は削除してしまうこととしました。本記事を描いた時点でNormalize.cssはバージョン3.0.1、Bootstrap.jsはバージョン3.2.0でした。

単に各サイトからファイルをダウンロードして、Bootstrap.jsはコピーライト表示とGlyphiconに関する部分だけを抜き出したファイルを作成し、必要なファイルをstaticディレクトリ以下に配置します。 筆者のstaticディレクトリ内は以下の様になりました。

static
├── combined
│   └── rMJ_CwK2.css
├── css
│   ├── glyphicons.css
│   └── normalize.css
├── fonts
│   ├── glyphicons-halflings-regular.eot
│   ├── glyphicons-halflings-regular.svg
│   ├── glyphicons-halflings-regular.ttf
│   └── glyphicons-halflings-regular.woff
└── tmp
    ├── autogen-SWBGXCgb.js
    └── autogen-vozS04Wr.css

3 directories, 5 files

これに伴ってFoundation.hsdefaultLayout関数の中身を再び多少変更します。

        pc <- widgetToPageContent $ do
            $(combineStylesheets 'StaticR
                [ css_normalize_css
                , css_glyphicons_css
                ])
            $(widgetFile "layout/application")
        giveUrlRenderer $(hamletFile "templates/layout/wrapper.hamlet")

このcombineStylesheetsおよびcombineScripts関数はSettings/StaticFiles.hsで定義されています。

-- | This generates easy references to files in the static directory at compile time,
--   giving you compile-time verification that referenced files exist.
--   Warning: any files added to your static directory during run-time can't be
--   accessed this way. You'll have to use their FilePath or URL to access them.
$(staticFiles Settings.staticDir)

combineSettings :: CombineSettings
combineSettings = def

-- The following two functions can be used to combine multiple CSS or JS files
-- at compile time to decrease the number of http requests.
-- Sample usage (inside a Widget):
--
-- > $(combineStylesheets 'StaticR [style1_css, style2_css])

combineStylesheets :: Name -> [Route Static] -> Q Exp
combineStylesheets = combineStylesheets' development combineSettings

combineScripts :: Name -> [Route Static] -> Q Exp
combineScripts = combineScripts' development combineSettings

また、その上のコメントに書かれている通り、css_normalize_csscss_glyiphicon_cssSettings/StaticFiles.hsコンパイルした際に定義される、静的ファイルのパスを表現する変数です。 yesod develのプロセスを起動しっぱなしにしている場合、staticディレクトリ以下の変更だけではSettings/StaticFiles.hsはリビルドされないため、Vimでファイルを開いて:wで保存するなどしないと、上の変更はエラーになります。

なお、確実にリビルドさせるにはcabal buildコマンドをプロジェクトルートで走らせます。

おわりに

HamletファイルとCassiusファイルを編集してそこそこ使い物になりそうなレイアウトを作るところまで行くつもりでしたが、思いの外説明が長くなってしまったのでここで一度記事を切ろうかと思います。 次回はレイアウトをして、静的なWebページとして公開できるくらいまでアプリケーションの完成度を高めます。

HaskellのYesodでWebアプリ開発入門 (2)

前回の記事

HaskellでYesodを使ってWebアプリケーションを作ってみている際のメモ記事です。

前回はインストールと初回起動までやりました。

Yesodのディレクトリ構造

前回は何も考えずに起動しただけでしたので、次はお決まりの流れでScaffoldのディレクトリ構造の確認をしたいと思います。 現在プロジェクトディレクトリに含まれるファイルは以下の様な感じですね。Homebrewでインストールしたtreeコマンドの結果からdistディレクトリを省略したものです。

sample
├── Application.hs
├── Foundation.hs
├── Handler
│   └── Home.hs
├── Import.hs
├── Model.hs
├── Settings
│   ├── Development.hs
│   └── StaticFiles.hs
├── Settings.hs
├── app
│   ├── DevelMain.hs
│   └── main.hs
├── config
│   ├── client_session_key.aes
│   ├── favicon.ico
│   ├── keter.yaml
│   ├── models
│   ├── mysql.yml
│   ├── postgresql.yml
│   ├── robots.txt
│   ├── routes
│   └── settings.yml
├── deploy
│   └── Procfile
├── devel.hs
├── dist
│   ├─ ...
│   ...
├── messages
│   └── en.msg
├── static
│   ├── combined
│   │   └── rMJ_CwK2.css
│   ├── css
│   │   ├── bootstrap.css
│   │   └── normalize.css
│   └── img
│       ├── glyphicons-halflings-white.png
│       └── glyphicons-halflings.png
├── templates
│   ├── default-layout-wrapper.hamlet
│   ├── default-layout.hamlet
│   ├── homepage.hamlet
│   ├── homepage.julius
│   └── homepage.lucius
├── tests
│   ├── HomeTest.hs
│   ├── TestImport.hs
│   └── main.hs
└── yesod-devel
│   ├── arargs.txt
│   └── ghcargs.txt
└── yesod-sample.cabal

23 directories, 148 files

RoRをある程度触ったことのある人ならなんとなく想像がつくかと思いますが、ざっくりと分かる範囲でファイルの役割を書き出してみます。 ファイルの中身を見ながら読んでいただけるとなんとなくわかっていただけるかと。

(以下の表には想像も含まれていますので、正しいことが分かり次第更新します)

ディレクトリやファイル 役割
Application.hs app/main.hsから呼び出されるアプリケーションの本体を作る関数makeApplicationが定義されている
Foundation.hs Application.hsでインスタンスが作成されるアプリケーションの本体が定義されている
Handler/ いわゆるコントローラの働きをするHandlerの定義ファイルが置かれるディレクトリ。中のファイルの詳細は後で書くので省略
Import.hs プロジェクト内のすべてのモジュールで読み込まれるYesodなどのモジュールをimportしたモジュール
Model.hs, config/models O/R mapperであるPersistentで使用されるモデル関連ファイル
Settings/Development.hs Development環境用を区別するための関数が定義されている
Settings/StaticFiles.hs 静的ファイルの読み出しのための関数を定義したファイル
Settings.hs いろいろな設定が書かれているファイル。静的ファイルのURL設定やHTMLファイルなどの構文解析に関する情報が書かれている
app/DevelMain.hs ghciでアプリケーションの環境を使うためのメイン・エントリ・ポイント
app/main.hs 通常のアプリケーションのメイン・エントリ・ポイント
config/client_session_key.aes コンパイル時に自動生成されるユーザのクッキー情報を暗号化するためのファイル
config/favicon.ico, config/robots.txt 普通のファビコンやらrobots.txtやら
config/keter.yaml, deploy/Procfile デプロイに関するファイル。後者にはHerokuへデプロイするための情報が書かれている
config/mysql.yml, config/postgresql.yml 前回も紹介したデータベース設定に関するファイル
config/routes URLとコントローラの対応関係を記述するファイル
config/settings.yml ルートURLやポートなど、アプリケーション全体に関する情報が記述されたファイル
dist/build/sample/sample アプリケーションの実行ファイル。本番環境ではこの実行ファイルのプロセスからデーモンプロセスを作る
devel.hs デバッグ環境のアプリケーションのメイン・エントリ・ポイント
massages/ i18n(国際化対応)のための辞書ファイルを置くディレクトリ
sample.cabal 前回紹介したcabalのための設定ファイル
static/ CSSJavaScriptライブラリや画像などの静的ファイルを置くディレクトリ。デフォルトではBootstrapのバージョン2.3.2(古い!)のCSSと画像ファイル、Normallize.cssのバージョン2.1.2(古い!)とそれらCSSをつなげたCSSファイルが含まれている
templates 動的な部分を含む(テンプレートを利用する)HTMLやJavaScriptCSSファイルを置くディレクトリ。Yesodでは標準でHTMLにHamlet、JavaScriptにJulius、CSSにLuciusかCassiusという言語を使うようになっている
tests/ テストコードを置くディレクトリ。Ruby並に自然な形でテストコードが書かれていてワクワクする
yesod-devel/ デバッグ環境で実行する際の設定を記したファイル、かな

思っていたよりも見通しはスッキリ。 Haskellの構文の柔軟さのお陰で全体的にファイルの可読性が高く、性能を追求したりし始めるまでは案外簡単に開発出来てしまうのでは? という期待が膨らみます。

ghci

表のDevelMain.hsの説明にちょろっと書きましたが、Yesodではアプリケーション実行時と同じ環境でghciを利用するための設定が追加されています。

DevelMain.hsの冒頭に説明がありますが、ghciを

$ cabal repl --ghc-options="-O0 -fobject-code"

として実行すると、アプリケーション使用時にロードされるパッケージを全てロードした状態でHaskellの対話環境を使えます。

とてつもなく便利ですね。

TemplateHaskell

ファイルを見ていくと、少ししかHaskellを学んだことがない人は「おややっ??」となったのではないでしょうか。 きっとこれを編集すればいいんじゃないかな、というHandler/Home.hsにも以下の様な見慣れない(というかjQueryみたいな)式があります。

$(widgetFile "homepage")

これはTemplateHaskellというHaskellにおけるマクロのようなもので、こいつを使うとLispのquoteとeval, funcallでやるようなことが書けてしまうというすぐれものです。

widgetFileはYesodの関数ではなく、Scaffoldで生成されたSettings.hsの中に含まれており、型を確認することができます。

widgetFile :: String -> Q Exp
widgetFile = (if development then widgetFileReload
                             else widgetFileNoReload)
             widgetFileSettings

つまり、先の式はwidgetFile関数が"homepage"というStringを元にQuoteモナド(Lispのquoteされた式みたいなもの)をつくり、それを$()で評価した結果を表していたわけですね。

Yesod公式のScaffoldに関するエントリによると、widgetFiletemplatesディレクトリ以下にある適用した文字列を名前として持つHaml, Julius, Lucius, Cassiusファイルをパースして1つのウィジェット(アプリケーション内でのレスポンスの表現?)にまとめてくれる関数のようです。

この関数はFoundation.hs内のAppインスタンスの定義にあるdefaultLayout関数でも使われています。

    defaultLayout widget = do
        master <- getYesod
        mmsg <- getMessage

        -- We break up the default layout into two components:
        -- default-layout is the contents of the body tag, and
        -- default-layout-wrapper is the entire page. Since the final
        -- value passed to hamletToRepHtml cannot be a widget, this allows
        -- you to use normal widget features in default-layout.

        pc <- widgetToPageContent $ do
            $(combineStylesheets 'StaticR
                [ css_normalize_css
                , css_bootstrap_css
                ])
            $(widgetFile "default-layout")
        giveUrlRenderer $(hamletFile "templates/default-layout-wrapper.hamlet")

コメントにも書かれていますが、templates/default-layout-wrapper.hamletにページ全体のレイアウト、templates/default-layout.hamletにBODYのレイアウトが記述されているようです。

ここまで見てから再度Handler/Home.hsgetHomeR関数の全体を見ると

getHomeR :: Handler Html
getHomeR = do
    (formWidget, formEnctype) <- generateFormPost sampleForm
    let submission = Nothing :: Maybe (FileInfo, Text)
        handlerName = "getHomeR" :: Text
    defaultLayout $ do
        aDomId <- newIdent
        setTitle "Welcome To Yesod!"
        $(widgetFile "homepage")

ざっくり、default-layout-wrapper.hamlet, default-layout.hamlet, homepage.hamlet, homepage.julius, homepage.luciusをくっつけて、widgetFile関数を呼ぶまでに用意したformWidgetやらformEnctypeやらsubmissionやらやらが展開されて、レスポンスとして返されてるんだろうなあ、というくらいは分かるようになりました。

それではいよいよ、ここらへんをいじって静的ファイルを自分なりに作り替えてみたいと思います。

ちなみに、Template Haskellの簡単な説明ならruiccさんの記事がわかりやすかったです。

プロジェクトのGit管理

ところで、プロジェクトの生成の際に.gitignoreファイルも一緒に作成されるので、編集を始める前にプロジェクトのディレクトリをGitで管理しておくと、この後にScaffoldのコマンドで行なった変更点をgit diffで見れたり、簡単に変更をやめたりできるので便利です。

ただ、もしプロジェクトをオープンソースで管理したりするのであれば、パスワードが含まれるconfig/mysql.ymlconfig/postgresql.ymlはデフォルトでは.gitignoreに含まれていないので追加して、本番環境へデプロイする際に別個で送信するべきかと思います。 (Capistranoを使うのはなんだかちょっとなあ、っていう感じなんですけどね...)

Vimシンタックスハイライト

編集を始める前に準備をもう一点。 テンプレート用の言語達をハイライトしてくれるVimプラグインをインストールしておきます。

幸い、pbrisbinさんのhtml-template-syntaxというレポジトリプラグインを追加すれば、簡単にテンプレートファイル達をシンタックスハイライトすることができました。

Warningの修正

yesod initでダウンロードされるscaffoldではYesodの最新版に対応していないらしく、最初からいくつかのWarningがでてしまっているので、それを修正します。

まずはModel.hs内でimport Preludeと書かれていますが、Preludeはデフォルトでimportされるので消去します。

次に、Foundation.hs内で使用されているgiveUrlRendererがdeprecatedとなっているので、代わりにwithUrlRendererを使用します。

これで鬱陶しいWarningが消えたはずです。 なんで修正されないんだろう...

テンプレートファイルの書き換え

まずは単に、Scaffoldで用意されているHomeハンドラを編集して、Webのトップページらしくしてみます。

せっかく作ってくれた(といってもGithubからコピーしてきているだけだが)Homeハンドラやらやらなので少し躊躇しましたが、サンプルプログラムもそんなに面白く無いのでスパッと全てなくしちゃいます。

Homeハンドラに関する記述があるのはconfig/routes, Handler/Home.hs, templates/homepage.*, sample.cabal, Application.hsの5箇所のファイルですが、後者2つはimportなどの宣言を行っているだけなので、前の3箇所だけを変更します。

ひとまず、config/routesHandler/Home.hsは以下のように編集しました。

/static StaticR Static getStatic
/auth   AuthR   Auth   getAuth

/favicon.ico FaviconR GET
/robots.txt RobotsR GET

/ HomeR GET
{-# LANGUAGE OverloadedStrings #-}
module Handler.Home where

import Import

getHomeR :: Handler Html
getHomeR = defaultLayout $(widgetFile "home/index")

とりあえず表示するためにtemplates/home/index.hamletという名前でHello, World!だけのファイルをつくります。

<h1>Hello, World!

これらの変更を行ってからyesod develを叩けばlocalhost:3000でHello, World!の文字が見られると思います。

ルーティングはRoRみたいにStaticPagesとかいう名前のハンドラを作ってその中のメソッドでURLによってGETの結果を変える、という風にしようかとも考えましたが、それは別にRESTでもなんでもないし、widgetFile関数でうまくテンプレートファイル達をくっつけてくれることを考えたらトップページと概要ページ、コンタクトページとかで1つづつディレクトリを作るほうがいいように思えたのでこうしてみました。

よくよく考えて見れば、よくやるHTMLはviewとかのディレクトリに置いてCSSJavaScriptはassetsとかに置くという区別は、DSLを使うなら同じような階層構造を持つディレクトリが増殖するだけであまり意味ない気がするのですが、どうなんでしょうかね。VimでHTMLとCSSを並べて開きたい時とかも面倒なだけだし、Assets precompileするとしても拡張子で判断がつく上にむしろwidgetFileの形式のほうが依存関係はわかりやすいし...

ちなみに、サンプルプログラムのHandlerの一番上で宣言されていたTupleSectionsというエクステンションは(, True)\x -> (x, True)として解釈してくれるようなものらしいです。これはsampleForm関数で使用していただけなので消しても問題ありませんが、OverloadedStringsはダブルクオーテーションの文字列をByteStringとして扱ってくれ、性能改善になるので一応残しておきました。

Handlerの追加

早くテンプレートファイル達をいじってHamletやらやらと戯れたいところですが、さすがに見れるファイルが1つだけだとヘッダも作りようがないので、コンタクトページだけ追加しておきます。

Handlerを追加するには、Handler以下にモジュールを作るだけでなく、sample.cabalの依存関係に追加したモジュールを加えて、Application.hsで追加したモジュールをインポートした上で、config/routeでルーティングの設定をする必要があります。地味に面倒臭いですね。

というわけで、Scaffoldを使いました。

$ yesod add-handler
Name of route (without trailing R): Contact
Enter route pattern (ex: /entry/#EntryId): /contact
Enter space-separated list of methods (ex: GET POST): GET
$

作成したHandlerを見ると

module Handler.Contact where

import Import

getContactR :: Handler Html
getContactR = error "Not yet implemented: getContactR"

と、ただエラーを投げる関数になっていますので、これをHomeハンドラと同じように編集し、とりあえずのHamletファイルも追加しておきます。

{-# LANGUAGE OverloadedStrings #-}
module Handler.Contact where

import Import

getContactR :: Handler Html
getContactR = defaultLayout $(widgetFile "contact/index")

templates/contact/index.hamlet

<h1>Contact

Homeの変更からデバッグ環境のサーバを起動したままでいれば、変更が勝手に反映されてlocalhost:3000/contactでコンタクトページを表示できます。

Play! frameworkのActivatorなんかよりよっぽど高速でいいですね。

おわりに

だんだんそのユーザとしてはYesodの全体像が見えてきて楽しくなってきました。

本当はテンプレートファイルを綺麗に編集するところまで書こうと思っていたのですが、無意味に長くなってきてしまったのでそれらは次回に回します。

HaskellのYesodでWebアプリ開発入門 (1)

この記事について

ふと思い立ってHaskellでWebアプリ開発をしたいなと思ったところ、乗り遅れている感は否めないものの、Yesodというフレームワークが一番有名らしいので触ってみることにした。

この記事は、環境構築などの手順を忘れてしまうと勿体無いので、踏んだ手順をメモしておくためのものです。

なので、一応将来的にはEsqueletoも使ってちゃんとしたTDDでRESTfulなWebアプリにしたいと思っていますが、気分が変われば途中で書くのをやめるかもしれないので、入門記事としては期待できないかと思います。

※ 2014年9月13日に Mac OS XのためのHaskell開発環境 - プログラミング芸術論 という記事を書いた関係で、使用バージョンや導入方法の情報を更新しました。

環境

当分は手元のMacBookを使うので、

  • Mac OS X Mavericks (10.9.4)
  • GHC version 7.8.3
  • Yesod version 1.2.12.8
  • Cabal library version 1.20.0.3
  • cabal-install version 1.20.0.2

な環境でのお話です。

ちなみに、筆者のスペックは

  • O'RaillyのReal World Haskellをある程度まで読んだが、もう内容忘れた
  • Webアプリケーション開発はRuby on Rails Tutorialを読んで、アプリケーションを作ってみたり商用プログラミングも少しだけ必要に迫られてやったことがある

という程度なので、解説はほぼできないも同然です。

開発環境の構築

GHCを始めとするHaskellの開発環境、cabalなどの使い方については Mac OS XのためのHaskell開発環境 - プログラミング芸術論 にまとめてありますので、そちらをご覧ください。

Yesodのインストール

まず、Yesodをグローバルにインストールします。

もともとはプロジェクトのsandboxを作成してからYesodもローカルのインストールを行うだけにしていたのですが、それだとYesodの実行ファイルへのパスが通らずにcabal exec yesod develが動かなかったため、Yesodだけはグローバルにインストールするようにしました。

このcabal exec yesod develにパスが通らない件はYesodのGithubレポジトリのissue Cannot use yesod-bin from within the cabal sandbox · Issue #737 · yesodweb/yesod · GitHub でも議論されていますが、この記事を書いている現在では有効な解決策は見つかっていないようなので、プロジェクト内で使うために計2回Yesodの長時間コンパイルを味わうこととなりますが、グローバルにインストールするやり方が一番問題なく済むかと思います。

$ cabal update
$ cabal install yesod-bin --max-backjumps=-1 --reorder-goals -j

reorder-goalsは他のパッケージの依存バージョンなどの関係でインストールするパッケージのバージョンがあわない場合に頑張ってくれるオプションで、--max-backjumps-1を指定することでその試行回数を無制限に設定します。

たくさんのパッケージをインストールするので、驚くほど時間が掛かります。

ちなみに私の環境では上記cabal install実行時にhaskell-platformによってインストールされていたalexとhappyを使おうとしてバージョンが古くてインストールがコケてしまったので、別個にcabal install alex happy -jとしてやる必要がありました。

プロジェクトの作成

新しいプロジェクトはyesod initで作成でき、対話式でプロジェクト名と使用するデータベースソフトを尋ねられます。

$ yesod init
Welcome to the Yesod scaffolder.
I'm going to be creating a skeleton Yesod project for you.

What do you want to call your project? We'll use this for the cabal name.

Project name: yesod-sample
Yesod uses Persistent for its (you guessed it) persistence layer.
This tool will build in either SQLite or PostgreSQL or MongoDB support for you.
We recommend starting with SQLite: it has no dependencies.

    s      = sqlite
    p      = postgresql
    pf     = postgresql + Fay (experimental)
    mongo  = mongodb
    mysql  = MySQL
    simple = no database, no auth
    url    = Let me specify URL containing a site (advanced)

So, what'll it be? mysql
That's it! I'm creating your files now...

---------------------------------------

                     ___
                            {-)   |\
                       [m,].-"-.   /
      [][__][__]         \(/\__/\)/
      [__][__][__][__]~~~~  |  |
      [][__][__][__][__][] /   |
      [__][__][__][__][__]| /| |
      [][__][__][__][__][]| || |  ~~~~
  ejm [__][__][__][__][__]__,__,  \__/


---------------------------------------

The foundation for your web application has been built.


There are a lot of resources to help you use Yesod.
Start with the book: http://www.yesodweb.com/book
Take part in the community: http://yesodweb.com/page/community


It's highly recommended to follow the quick start guide for
installing Yesod: http://www.yesodweb.com/page/quickstart

If your system is already configured correctly, please run:

   cd yesod-sample && cabal install -j --enable-tests --max-backjumps=-1 --reorder-goals && yesod devel

なんか人出てきた...

今回はyesod-sampleという名前のプロジェクトにして、使用するDBMSMySQLを選びました。

依存パッケージのインストール

アプリケーションを起動するには、依存パッケージ(再びYesodの全体を含む...)のインストールとプロジェクト全体のコンパイルおよびインストールをする必要があります。

yesod initの最後に出てきたコマンドではグローバルにプロジェクトをインストールしてしまっていますが、プロジェクトの依存パッケージをグローバルにインストールしてしまうと他のHaskellプロジェクトに影響が出るので、cabalのsandbox機能を有効化してからYesodをインストールします。

$ cd yesod-sample
$ cabal sandbox init
$ cabal install -j --enable-tests --max-backjumps=-1 --reorder-goals

上のコマンドを叩くだけで自動的にプロジェクト全体のコンパイルが行われた上で、プロジェクト全体が他のcabalでインストールするライブラリと同じようにインストールされた状態になります。

--enable-testsはテストのための依存パッケージもインストールするオプションです。 yesod-sample.cabalを見ると依存パッケージやコンパイル時に使用されるオプションなどがデフォルトでどう設定されているかわかります。

ちなみに、build-dependsの記述にあるpersistentっていうのが、Yesodを使う際に現状最もポピュラーなO/R mapperみたいですが、MySQLだけの構文などには弱いみたいなので、ゆくゆくはその拡張のEsqueletoを使ってみたいなと思っています。

で、いよいよサーバの起動ですが、今回はデータベースソフトにMySQLを指定してしまったので、先にその設定を行う必要があります。

データベースの設定とアプリケーションの起動

データベースの情報はconfig/mysql.ymlにあり、RoRconfig/database.ymlとほぼ同じなので、特に迷うことはないかと思います。とりあえずDevelopment環境とTest環境のデータベース設定をしっかりとやって、MySQLでユーザとデータベースの作成を行なっておけばOKでしょう。

ちなみに、データベースの設定ファイルはYAML形式なので、パスワードが数字だけだったり空だったりする場合にはダブルクオーテーションで囲む必要があります。

設定が完了したら、以下のコマンドでアプリケーションを起動します。

$ yesod devel

自動的にソースコードがビルドされ、Scaffoldに含まれているデータベースマイグレーションが行われた後、アプリケーションサーバが起動します。なぜか筆者の環境ではマイグレーションを含む一回目の起動の際にDevel application launchedの表示がされなかったのですが、問題なく起動していました。

localhost:3000にアクセスしてScaffoldのホームページが表示されていれば、起動までのプロセスは完了です。

おわりに

無事環境構築とScaffoldのアプリケーションを起動することができました。

なお、プロジェクトを削除する際には依存パッケージも全てプロジェクトディレクトリ以下に含まれているので、単にディレクトリを削除するだけで済みます。

$ rm -r yesod-sample

次回はScaffoldで生成されたファイルの確認とハンドラの追加までです。

tmuxのバージョンアップには要注意

tmux 1.9a

久しぶりにbrew outdatedしてみたらtmuxの新しいバージョンが利用可能になっていたので、インストールした。

protocol version mismatch

すると、protocol version mismatch (client 8, server 7)という見慣れないメッセージが出てセッションを回復できない。どうやら、バージョンアップしたことでセッションの保存形式か何かが変わったようで、セッションを再読み込みできなくなってしまったみたいだ...
vimいっぱい開いてたのでスワップファイルの処理に朝からイライラしてしまった。

結局

というわけで、tmuxのバージョンアップをする時には注意しましょう、というだけの話でした。
結局tmux kill-serverも効かなかったので、単にps aux | grep tmuxでtmuxのPIDを探してkillで済ませました。