先日から引き続きhaskellを使っていこう。
yesodというwebのフレームワークを利用しながら開発をしていくつもりなので、
- yesodでよく分からないなりにアプリを構築
http://www.yesodweb.com/book - 書籍を写経しつつ基礎を学習
この二つのアプローチで学習していく
STEP UP Haskell!! -2-
yesod環境の構築
まずはyesodでの開発を可能にする。
Yesod quickstartに従えばできるはず。
….
と思っていた時期が私にもありました。
私の場合はstack build yesod-bin cabal-install --install-ghc
ここで失敗した。。。
理由はGHC panic when building Stack on macOS Sierraというghc7系とMac OS Sierraの相性の問題の模様。
最新版の8.0.2では修正が入っているようだが、まだリリースされていない(2017/01/08時点)
また、8.0.2で仮に動作したとしてもyesodに必要なlib郡が全部入るのかどうかも分からない。
(stackageにはまだ8.0.2のyesod libが定義されていないし)
詰んだ。と思ったが要はOSを変えればいいのだ。
なのでwindowsに逃げるなりvmを立ち上げるなりをすればいいのだ。
調べたところによるとhaskell stackがdockerサポートを結構やっているらしく、
optionを一つ追加するだけでstack
コマンド全般がdockerのcontainer上で動作するように設定が可能っぽい。
以下のような手順でとりあえずyesodサンプル(db migrateからlocalhost:3000でのアクセスまで)ができるようにした。
docker install
(これは各OS毎に異なるかも、Macはdocker platformがあったのでdmgからinstallした)
stack docker pull
(stack.ymlに準拠したfpco/stack-buildという名称のimageが取得できる)
stack new my-project yesod-mysql && cd my-project
ここまでやったらstack.yml
が出来上がるので、
docker:
enable: true
これを追記。
x stack build yesod-bin cabal-install --install-ghc
(マニュアルによるとこれだが、install-ghcだとstackがいれたやつになりそうだったからやめた)
stack build yesod-bin cabal-install
(こっちにした)
stack build
これでstack関係はOK。
# ここからmysqlのinstall
brew install mysql
mysqld --initialize --explicit_defaults_for_timestamp
(passwordメモっとくこと)
mysql_secure_installation
mysqld
これでmysqlは起動する。
で、mysqlの設定をyesodプロジェクトの方に記載する必要があるので、
config/setting.yml
の該当箇所を以下のように編集
database:
user: "_env:MYSQL_USER:yourusername"
password: "_env:MYSQL_PASSWORD:yourpassword"
host: "_env:MYSQL_HOST:192.168.11.2" # your host IP address, not hostname
port: "_env:MYSQL_PORT:3306"
# See config/test-settings.yml for an override during tests
database: "_env:MYSQL_DATABASE:yourdbname"
poolsize: "_env:MYSQL_POOLSIZE:10"
hostはlocalhostだとsocketを利用しにいくので、必ずIPで指定。
というのもdocker上で動いているcontainerからhostマシンのmysqlに接続にいくので
sockerファイルの共有の設定がめんどくさいからだ。ここのIPアドレスはhostマシンでifconfigで出てるやつを設定しておけばいい。
公式のドキュメントによればcontainerとhostマシンのネットワークは同様に使えるとのことだった。
最後に
stack --docker-run-args='--net=bridge --publish=3000:3000' exec -- yesod devel
この--docker-run-args
とか不要だと思うのだけど何故かつけないと
yesodのdevel serverにhostマシンのブラウザからアクセスができなかった。
今後の世界を考えてもdocker imageでcontainerを作ったり破棄したりを繰り返すようになるだろうし、docker imageの中に含まれていてもなんら問題はないだろう。
(むしろdeploy時にも楽ができるようになるんじゃないかと目論んでいる)
どちらにせよserveするときにはimage化したいし。
refs: https://docs.haskellstack.org/en/stable/docker_integration/
以降はチュートリアルをやってみる。
helloworld.hs
railsとかと違って全然知識不足により即実装とはなかなか行きづらいのでチュートリアル。
sample1 helloworld
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data HelloWorld = HelloWorld
mkYesod "HelloWorld" [parseRoutes|
/ HomeR GET
|]
instance Yesod HelloWorld
getHomeR :: Handler Html
getHomeR = defaultLayout [whamlet|Hello World!|]
main :: IO ()
main = warp 3000 HelloWorld
これをコピペして実行。
解説が載っているので割愛するが、一部分からないのがQuasiQuotes
という単語。
準クォートという名称らしいのだが、
http://d.hatena.ne.jp/ruicc/20111015/1318630433
http://haskell.g.hatena.ne.jp/mr_konn/20101210/quasiquotes
http://www.slideshare.net/konn/metaprogramming-in-haskell
この辺りの解説くらいしか目ぼしいものが見つからない。
基礎勉強の方のアプローチでそのうち出てくることを祈ろう。
とっても薄い理解だと、クォート(”)は文字列リテラルを表しているように、
何か別な記号や関数名(?)に意味を持たせて実行可能にする。
(クォートではないが表記としては同じような書き方ができるから準クォート?)
間違いが発覚したり、後日理解が進んだら訂正します。
この準クォートに当たるのがparseRoutes。
名前からするとyesodが用意してくれたpath名のparserか。
HomeR
は/
にGET
リクエストが来たら答える。というコード。
HomeRのRはresourceの意味らしい。慣習的につけているっぽい
規約により、http methodとresource nameをつなげてhandlerを定義する。
今回はGET
+ HomeR
なのでgetHomeR
をhandlerとして定義している。
whamletはQuasiQuotesらしい。(やっぱ微妙にわかりにくい。。。)
このケースだとHamlet Syntax(Hamletはyesodのtemplate engine)からWidgetを作り出すとのこと。
時たま解説にTH function
というのが出てくるがおそらくTemplateHaskell function
の略っぽい。
ddump-splices
このオプションを入れてghc
を実行すると構文木を接合した状態のものを出力してくれる。
上記準クォートの処理結果を必要なところで展開して見せてくれるような機能だと理解した。
stack
コマンドを利用しているならstack runghc -- -ddump-splices helloworld.hs
で実行可能だ。
あとの細かいことは後述されるとのことだったので、他のチャプターを読んだ時に出てくるのだろう。
sample2 links
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data Links = Links
mkYesod "Links" [parseRoutes|
/ HomeR GET
/page1 Page1R GET
/page2 Page2R GET
|]
instance Yesod Links
getHomeR = defaultLayout [whamlet|<a href=@{Page1R}>Go to page 1!|]
getPage1R = defaultLayout [whamlet|<a href=@{Page2R}>Go to page 2!|]
getPage2R = defaultLayout [whamlet|<a href=@{HomeR}>Go home!|]
main = warp 3000 Links
Linksアプリケーションが出来上がった。
注目すべきは
/page1
と/page2
が出来上がっていること- aタグの中にdata constructorであるPage1Rなどが渡されていること
page1やpage2を足したのでhandlerをそれぞれ命名規約通り(getPage1R、getPage2R)を定義する。
Page1Rとかは
renderRoute HomeR = ([], [])
renderRoute Page1R = (["page1"], [])
renderRoute Page2R = (["page2"], [])
こんな感じで内部的には処理されているらしいので、所詮ただのタプルである(第一引数はpath、第二引数はquery)
これをaタグのリンクにいれられるため、URLの変更に強かったりする。
確かrailsでもActiveRecordのmodelをa_tagに渡すことができたような気がするがそれに近いのかもしれん。
sample3 json
{-# LANGUAGE ExtendedDefaultRules #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE QuasiQuotes #-}
{-# LANGUAGE TemplateHaskell #-}
{-# LANGUAGE TypeFamilies #-}
import Yesod
data App = App
mkYesod "App" [parseRoutes|
/ HomeR GET
|]
instance Yesod App
getHomeR = return $ object ["msg" .= "Hello World"]
main = warp 3000 App
これでjson responseが出来上がる。
{ msg: "Hello World" }
これはどの言語で書いてもこんなもんか。
今日はこんなところで。
では良いインプットと良いプログラミングを。