# HG changeset patch # User Daichi TOMA # Date 1373026999 -32400 # Node ID c0be45e5b32f3a5f0e86403f8bb75c81c7f8b4e3 Initial commit diff -r 000000000000 -r c0be45e5b32f haskell.html --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/haskell.html Fri Jul 05 21:23:19 2013 +0900 @@ -0,0 +1,653 @@ + + + + + + Haskell による Web Service 構築入門 + + + + + + + + + +
+ +
+

+ 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
+
+

+
+ + +
+

+ そして気づくことなど +

+

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

+

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

+

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

+
+ + +
+ + diff -r 000000000000 -r c0be45e5b32f pic/pong.png Binary file pic/pong.png has changed