Haskell による Web Service 構築入門

Daichi TOMA
Jul 6, 2013

Haskell とは

純粋関数型プログラミング言語

引数に関数を作用させて計算を行います。

命令型プログラミングとの違い

命令型プログラミング言語では、命令の並びをコンピュータに与えて、それを実行します。

関数型プログラミング言語では、何であるかという定義を与えます。

例:n番目のフィボナッチ数を求める関数の定義

fib 0 = 0
fib 1 = 1
fib n = fib (n - 1) + fib (n - 2)

Haskell の特徴

関数は副作用を持ちません

関数にできることは、何かを計算して結果を返すことだけです。
関数は同じ引数で呼ばれた場合、同じ値を返すことを保証します。

関数の正しさを簡単に推察でき、正しいと分かっている関数同士を容易に組み合わせることができます。

Haskell の特徴

Haskellは、結果が必要になるまで関数を評価しません。

遅延評価のため、無限の大きさのデータを扱うことが可能です。

例:奇数の無限のリストから最初の3つを入手する

take 3 [1,3..]

Haskell の特徴

型規則に従ってない式が存在しないことを保証します。

また型推論を持つため、すべての式に明示的に型を書く必要はありません。

コンパイラが非常に多くのバグを見つけてくれるので、コンパイルが通れば概ね思い通りに動きます。

よし、分かった。では Haskell を使う方法を教えてくれ。

一番手っ取り早い方法は、Haskell Platformを導入することです。

Haskellのコンパイラで最も広く使われている The Glasgow Haskell Compiler (GHC) や、便利な Haskell のライブラリのセットが付いてきます!

http://www.haskell.org/platform/にアクセスして、利用しているOS向けの指示に従ってください。

ghciで遊んでみる

ghci> 2+3
5
ghci> succ 3
4
ghci> 2 > 3
False
ghci> True && False
False

ghciで遊んでみる

gchi内で関数を定義する際はletが必要

ghci> let doubleMe x = x + x
ghci> doubleMe 4
8
ghci> let doubleUs x y = doubleMe x + doubleMe y
ghci> doubleUs 2 3
10
ghci> doubleUs 1.2 4.5
11.4

Haskell Platform をインストールしたら

Terminal を開き、

$ ghci
とタイプすることで、対話モードが起動できます。

対話モードでは、実際に関数を呼び出して、結果を直接見ることができます。

対話モードを終了するには、

ghci> :q
とタイプし、ENTERを押します。

なぜ Haskell で Web Service を書くのか

Haskell の Web Framework

Warp

軽量、高速 HTTP サーバです。

Haskell の軽量スレッドを活かして書かれています。

Pong benchmark (req/s)

とりあえず、Warp 入れてみる

$ cabal install warp

簡単なプログラム

{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)

application _ = return $
  responseLBS status200 [("Content-Type", "text/plain")] "Hello World"

main = run 3000 application

このソースコードを読み解いていきます。

Haskell は型を見れば多くのことが分かる言語です。
ということで、関数の型を見ていきます!

関数の型を確認する

まず、対話モードを開きます。

$ ghci

Wai 及び Warp の module を import します。
Wai というのは、Web Application Interface の略で、Web サーバと Web アプリケーション間の共通のプロトコルを取り扱います。

ghci> :module +Network.Wai
ghci> :module +Network.Wai.Handler.Warp

型を教えて貰うには、:t コマンドに続けて正しい式を入力します。

ghci> :t run
run :: Port -> Application -> IO ()

run

ghci> :t run
run :: Port -> Application -> IO ()

run は、Port と Application を受け取って、IO () を返す関数だということが分かります。

Port は、Int の別名です。
別名などの定義は、:t ではみれないので、:i を使うとよいでしょう。
Haskellでは、関数は小文字、型名などは大文字で始まります。

Application は、なんでしょうか?

ghci> :i Application
type Application =
  Request -> Control.Monad.Trans.Resource.ResourceT IO Response

Monad に包まれていて少しわかりにくいですが、端的に言えば Request を受け取って Response を返す関数を表しています。

ResourceT は、リソースの解放を取り扱う Monad、 IO は、入出力を取り扱う Monad です。

Haskell では、関数が副作用を持つことは許されませんが、IO アクションによって IO 操作を処理系に押し付けています。

runとは結局なんぞや

{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)

application _ = return $
  responseLBS status200 [("Content-Type", "text/plain")] "Hello World"

main = run 3000 application

型情報を読むことでおおよそ分かったような気がしませんか!

Port 番号と、Request を受け取って Response 返す関数を受け取る
run は IO () を返すので、外界に影響を与える

実際の動作としては、この関数 run は受け取った Port 番号で、Application を実行します。

次に、Application の実装を見ていきます。

Hello Worldと出力する簡単なお仕事

application _ = return $
  responseLBS status200 [("Content-Type", "text/plain")] "Hello World"

まず引数で渡される Request は _ (Underscore) となっているので、使用していません。

responseLBS とは?

Prelude Network.Wai.Handler.Warp Network.Wai Network.HTTP.Types> :t responseLBS
responseLBS
  :: Status
     -> ResponseHeaders
     -> Data.ByteString.Lazy.Internal.ByteString
     -> Response

Statusと、ResponseHeaders、ByteStringを受け取り、Responseを返します。

簡単に説明すると、文字列からResponseを構築するためのコンストラクターです。

ByteStringは、Stringと比較して効率よく文字列を扱います。 プログラムの最初に書かれている OverloadedStrings という言語拡張は、ダブルクオートで囲んだ文字列を、ByteString リテラルとして扱ってくれます。

{-# LANGUAGE OverloadedStrings #-}

Hello.hs

{-# LANGUAGE OverloadedStrings #-}
import Network.Wai
import Network.HTTP.Types (status200)
import Network.Wai.Handler.Warp (run)

application _ = return $
  responseLBS status200 [("Content-Type", "text/plain")] "Hello World"

main = run 3000 application

おおよその内容は掴めた、では実行してみようではないか。

実行方法は2つあります。

$ runghc Hello.hs

$ ghc --make Hello.hs
$ ./Hello

サイトにアクセスしてみる

http://localhost:3000/

Hello Worldと表示されれば成功です。

さすがにこれは簡単すぎる…

こんなの30秒で書けるよという皆様のために、次に単純な Routing っぽいものを実装してみたいと思います。

Application が受け取る Request には、様々な情報が含まれています。
その中には pathInfo という、どこの path へアクセスしてきたかの情報があります。

pathInfoの情報をどう入手するか

実は超簡単です。

pathInfo request

pathInfo という関数に Request を渡すだけ!

Haskell では、レコード構文というデータ型のフィールドにアクセスするためのコードを自動生成する仕組みがあります。 レコード構文で、データ型を定義するとデータ型が作られるのと同時に、フィールド名でフィールドを取得する関数たちが自動で作られます。

ghci> :t pathInfo
pathInfo :: Request -> [Data.Text.Internal.Text]

pathによって返す関数を変えてみよう

まず、application で request を取り routes という関数に path を渡すように変更します。

application request = return $
    routes $ pathInfo request

routes は、path を受け取って response を返す関数です。

routes path = findRoute path routeSetting

routes では、routeSetting という List から path が一致するもの探します。

pathによって返す関数を変えてみよう

findRoute では、再帰的に List を探索しています。
一致するものがなければ、notFound という関数を返します。
一致するものがあれば、routeSetting に記載された関数を返します。

findRoute path [] = notFound
findRoute path ((p,f):xs)
    | path == p = f
    | otherwise = findRoute path xs

routeSetting = [([],                 index),
                (["hello"],          hello),
                (["welcome","world"],world)]

response関数を定義しよう

いくつか定義してみます。

notFound = do
    responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found"

index = do
    responseLBS status200 [("Content-type", "text/html")] $ "index page"

hello = do
    responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom"

world = do
    responseLBS status200 [("Content-type", "text/html")] $ "Welcome to Underground"

完成!

Source Code

application request = return $
    routes $ pathInfo request

routes path = findRoute path routeSetting

findRoute path [] = notFound
findRoute path ((p,f):xs)
    | path == p = f
    | otherwise = findRoute path xs
routeSetting = [([],                 index),
                (["hello"],          hello),
                (["welcome","world"],world)]
notFound = do
    responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found"
index = do
    responseLBS status200 [("Content-type", "text/html")] $ "index page"
hello = do
    responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom"
world = do
    responseLBS status200 [("Content-type", "text/html")] $ "Welcome to Underground"

main = run 3000 application

実際に動かしてみる!

シンプルなシステムですが、実際に動くのか確かめてみます。

"index page" と表示されるはず
http://localhost:3000/

"hello, my name is Tom" と表示されるはず
http://localhost:3000/hello

"welcome to Underground" と表示されるはず
http://localhost:3000/welcome/world

一致するpathがないので、"404 - File Not Found" と表示されるはず
http://localhost:3000/hogehoge

そして気づくことなど

あれ、Haskellのプログラムって短くね?

そうです、圧倒的な記述力も特徴なんです。

速くて安全なHaskellで、あなたもWeb Serviceを作って見ませんか?

参考文献