Haskell
HaskellDay 22

Haskell入門者がライブラリを触っちゃう!?

はじめに

これは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つあると思って…
1. Routing
2. HTML+CSS+Javascript
3. DataBase
YesodのようなフルスタックのWebFrameworkもありますが、Haskellはライブラリが豊富なのでこれらに対応するものが独立してあります。

Routing

WebサイトのURLとそこで表示するページを結びつけます。はじめに書いたようにscottyを使います。詳しいドキュメントはHackageでも見てください。
サンプルは実行してhttp://localhost:3000 にアクセスしてください。

sample
{-# 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パーサが利用できるので。

index
<!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>
parse&render
{-# 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をインストールしておいてください。

sample
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 ++ ";"
command
stack build&stack exec sample-exe | sqlite3
result
0|Hello|World

公式ドキュメントほぼまんまですw。ここでは仮想のDBを作成して作業しています。
次は実際のDBをいじりましょう!といきたいですが…
TemplateHaskellの知識が少々必要になります。
残念ながらWriterにはこの知識がないのでテキトーなことは言えません。
実際にDBを操作するときは、defineTableマクロでもろもろ自動導出してくれるのでかなり楽っぽいです。先ほどの操作を実際に発行するときはrunQueryで実行します。
以上です。ここらへんはぼくもちんぷんかんぷんです(すいません…)

おわり

とりあえず「Haskellでなんかしたい」みたいなのは叶えられました。
ここではそれぞれのライブラリについて断片的に紹介して終わりにしました。
haskell-jpのslackで質問に答えくださったみなさんには感謝しています。
コメントorマサカリは https://twitter.com/seatofhorse まで。
おわり。