+ Haskell による Web Service 構築入門 +
+
+ Daichi TOMA
+
+ Jul 6, 2013
+
# HG changeset patch
# User Daichi TOMA
+
+ 関数は副作用を持ちません +
+
+ 関数にできることは、何かを計算して結果を返すことだけです。
+ 関数は同じ引数で呼ばれた場合、同じ値を返すことを保証します。
+
+ 関数の正しさを簡単に推察でき、正しいと分かっている関数同士を容易に組み合わせることができます。 +
++
+ 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" ++
+ 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を作って見ませんか? +
++