#はじめに
これはAdventCalendar2017の22日目の記事です。
はじめまして。@brackss1です。
「すごいH本」を読み終わって何か作りたくなりました。そこで目を付けたのがWeb開発です。有名なWebFrameworkといえば Ruby on Rails, Node.js, Go on Revel, Django などが思いつきますが、Haskellにも Yesod, Spock などwikiに紹介されているだけでも14個のライブラリがあります。この中から今回はscottyを使いました。
#環境
Haskell使うにはStack(パッケージ管理)で神。
エディターはVSCodeを使いました。Haskero拡張インストールしてinteroをセットすれば補完できるのでなんとかなります。
せっかくなのでインターネット上にあっぷしたい。お手軽なのはGitHub.IOですが静的サイトしか公開できない(DBが使えない)ので、Herokuを使います。無料で使えるので選びました。(だいぶ制限されますが)
#Herokuについてちょっと
PaaS(Platform as a Service)。git&herokuコマンドでコマンドラインで操作できるので楽しい。以上。
#ライブラリ…
WebAppの要素は主に3つあると思って…
- Routing
- HTML+CSS+Javascript
- DataBase
YesodのようなフルスタックのWebFrameworkもありますが、Haskellはライブラリが豊富なのでこれらに対応するものが独立してあります。
#Routing
WebサイトのURLとそこで表示するページを結びつけます。はじめに書いたようにscottyを使います。詳しいドキュメントはHackageでも見てください。
サンプルは実行してhttp://localhost:3000 にアクセスしてください。
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
main :: IO()
main = scotty 3000 $ do
get "/" $ html "<h1>Hello!</h1>"
ただHello!と表示されます。POST通信はログイン機能で披露したいので、HTMLファイルのパースを紹介した後にしたいと思います。
外部鯖(ここではHeroku)に公開したい場合は次のようにします。
getPort :: Maybe Int -> Int
getPort (Just n) = n
getPort Nothing = 3000
port <- lookupEnv "PORT"
scotty (getPort $ read <$> port) $ do
#HTMLパース&レンダリング
HTML+CSS+Javascriptの書き方はこの記事では書きません。
実際に表示するためのhtmlをパース&レンダリングします。数あるライブラリの中でedeを選択しました。aesonのJSONパーサが利用できるので。
<!DOCTYPE html>
<html>
<head>
<title>{{ title }}</title>
</head>
<body>
<h1>{{ title }}</h1>
<ol>
{% for food in foods %}
<li>{{ food.value }}</li>
{% endfor %}
</ol>
</body>
</html>
{-# LANGUAGE OverloadedStrings #-}
import Web.Scotty
import Text.EDE
import Data.Aeson
import Data.Text(Text)
main :: IO()
main = do
path <- eitherParseFile "./html/index.html"
let env = fromPairs [("title" :: Text) .= ("好きな食べ物TOP3" :: Text)
,("foods" :: Text) .= (["フォー","餃子","石焼ビビンバ"] :: [Text])]
let txt = either error id $ path >>= (`eitherRender` env)
scotty 3000 $ do
get "/" $ html txt
自作のデータ型を使いたい場合は…
data Food = Food {name :: Text, from :: Text}
foods :: [Food]
foods = [Food "curry and rice" "India",
Food "hamburger" "America",
Food "ramen" "China"]
instance ToJSON Food
ToJSON
のinstanceにすればOKです。
#DBをさわる
リレーショナル・データベースとして有名なのはMySQLとPostgreSQLですが、Herokuで使えるのが後者だけということなのでそれを選びました。ライブラリは@khibinoさんのhaskell-relational-record(以後HRRと略)を使います。(cabalファイルに書くときはrelational-queryです。とりまsqlite3をインストールしておいてください。
import Data.Int(Int32)
import Database.Relational.Query
hello :: Relation () (Int32, String)
hello = relation $ return $ value 0 >< value "Hello"
world :: Relation () (Int32, String)
world = relation $ return $ value 0 >< value "World"
helloworld :: Relation () (Int32, String, String)
helloworld = relation $ do
h <- query hello
w <- query world
on $ h ! fst' .=. w ! fst'
return $ (,,) |$| h ! fst' |*| h ! snd' |*| w ! snd'
main :: IO ()
main = putStrLn $ show helloworld ++ ";"
stack build&stack exec sample-exe | sqlite3
0|Hello|World
公式ドキュメントほぼまんまですw。ここでは仮想のDBを作成して作業しています。
次は実際のDBをいじりましょう!といきたいですが…
TemplateHaskellの知識が少々必要になります。
残念ながらWriterにはこの知識がないのでテキトーなことは言えません。
実際にDBを操作するときは、defineTable
マクロでもろもろ自動導出してくれるのでかなり楽っぽいです。先ほどの操作を実際に発行するときはrunQuery
で実行します。
以上です。ここらへんはぼくもちんぷんかんぷんです(すいません…)
#おわり
とりあえず「Haskellでなんかしたい」みたいなのは叶えられました。
ここではそれぞれのライブラリについて断片的に紹介して終わりにしました。
haskell-jpのslackで質問に答えくださったみなさんには感謝しています。
コメントorマサカリは https://twitter.com/seatofhorse まで。
おわり。