Haskell による Web Service 構築入門
Daichi TOMA
Jul 6, 2013
Daichi TOMA
Jul 6, 2013
純粋関数型プログラミング言語
引数に関数を作用させて計算を行います。
命令型プログラミング言語では、命令の並びをコンピュータに与えて、それを実行します。
関数型プログラミング言語では、何であるかという定義を与えます。
例:n番目のフィボナッチ数を求める関数の定義
fib 0 = 0 fib 1 = 1 fib n = fib (n - 1) + fib (n - 2)
関数は副作用を持ちません
関数にできることは、何かを計算して結果を返すことだけです。
関数は同じ引数で呼ばれた場合、同じ値を返すことを保証します。
関数の正しさを簡単に推察でき、正しいと分かっている関数同士を容易に組み合わせることができます。
Haskellは、結果が必要になるまで関数を評価しません。
遅延評価のため、無限の大きさのデータを扱うことが可能です。
例:奇数の無限のリストから最初の3つを入手する
take 3 [1,3..]
型規則に従ってない式が存在しないことを保証します。
また型推論を持つため、すべての式に明示的に型を書く必要はありません。
コンパイラが非常に多くのバグを見つけてくれるので、コンパイルが通れば概ね思い通りに動きます。
一番手っ取り早い方法は、Haskell Platformを導入することです。
Haskellのコンパイラで最も広く使われている The Glasgow Haskell Compiler (GHC) や、便利な Haskell のライブラリのセットが付いてきます!
http://www.haskell.org/platform/にアクセスして、利用しているOS向けの指示に従ってください。
ghci> 2+3 5 ghci> succ 3 4 ghci> 2 > 3 False ghci> True && False False
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
Terminal を開き、
$ ghciとタイプすることで、対話モードが起動できます。
対話モードでは、実際に関数を呼び出して、結果を直接見ることができます。
対話モードを終了するには、
ghci> :qとタイプし、ENTERを押します。
軽量、高速 HTTP サーバです。
Haskell の軽量スレッドを活かして書かれています。
Pong benchmark (req/s)
$ 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 ()
ghci> :t run run :: Port -> Application -> IO ()
run は、Port と Application を受け取って、IO () を返す関数だということが分かります。
Port は、Int の別名です。
別名などの定義は、:t ではみれないので、:i を使うとよいでしょう。
Haskellでは、関数は小文字、型名などは大文字で始まります。
ghci> :i Application type Application = Request -> Control.Monad.Trans.Resource.ResourceT IO Response
Monad に包まれていて少しわかりにくいですが、端的に言えば Request を受け取って Response を返す関数を表しています。
ResourceT は、リソースの解放を取り扱う Monad、 IO は、入出力を取り扱う Monad です。
Haskell では、関数が副作用を持つことは許されませんが、IO アクションによって IO 操作を処理系に押し付けています。
{-# 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 の実装を見ていきます。
application _ = return $ responseLBS status200 [("Content-Type", "text/plain")] "Hello World"
まず引数で渡される Request は _ (Underscore) となっているので、使用していません。
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 #-}
{-# 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
Hello Worldと表示されれば成功です。
こんなの30秒で書けるよという皆様のために、次に単純な Routing っぽいものを実装してみたいと思います。
Application が受け取る Request には、様々な情報が含まれています。
その中には pathInfo という、どこの path へアクセスしてきたかの情報があります。
実は超簡単です。
pathInfo request
pathInfo という関数に Request を渡すだけ!
Haskell では、レコード構文というデータ型のフィールドにアクセスするためのコードを自動生成する仕組みがあります。 レコード構文で、データ型を定義するとデータ型が作られるのと同時に、フィールド名でフィールドを取得する関数たちが自動で作られます。
ghci> :t pathInfo pathInfo :: Request -> [Data.Text.Internal.Text]
まず、application で request を取り routes という関数に path を渡すように変更します。
application request = return $ routes $ pathInfo request
routes は、path を受け取って response を返す関数です。
routes path = findRoute path routeSetting
routes では、routeSetting という List から 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)]
いくつか定義してみます。
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"
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を作って見ませんか?