Mercurial > hg > Members > toma > osc2013
view haskell.html @ 9:95fa8bea3364
finish
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 06 Jul 2013 15:04:02 +0900 |
parents | eea79db7cd9e |
children |
line wrap: on
line source
<!DOCTYPE html> <!-- Google HTML5 slide template Authors: Luke Mahé (code) Marcin Wichary (code and design) Dominic Mazzoni (browser compatibility) Charles Chen (ChromeVox support) URL: http://code.google.com/p/html5slides/ --> <html> <head> <title>Haskell による Web Service 構築入門</title> <meta charset='utf-8'> <script src='http://web.amothic.com/html5slides/slides.js'></script> </head> <style> /* Your individual styles here, or just use inline styles if that’s what you want. */ </style> <body style='display: none'> <section class='slides layout-regular template-concurrency'> <article> <h1> Haskell による Web Service 構築入門 </h1> <p> Daichi TOMA <br> Jul 6, 2013 </p> </article> <article> <h3> Haskell とは </h3> <p> 純粋関数型プログラミング言語です。 </p> <p> 純粋なので、一度変数の値を設定すると、変更することは出来ません。 </p> <p> 関数型言語では、引数に関数を作用させていくことで計算を行います。 </p> <p> 他にも遅延評価や、強い静的型付けなどの特徴があります。 </p> </article> <article> <h3> Haskell の特徴 </h3> <p> 強い静的型付けにより型規則に従ってない式が存在しないことを保証します。 </p> <p> また型推論を持つため、すべての式に明示的に型を書く必要はありません。 </p> <p> しかしながら、実際にはどのような関数なのか表すために明示的に型を書くほうが良いです。 </p> <p> コンパイルが通れば概ね思い通りに動くのもHaskellの特徴です。 </p> </article> <article> <h3> Haskell の導入 </h3> <p> 一番手っ取り早い方法は、Haskell Platformを導入することです。 </p> <p> Haskellのコンパイラで最も広く使われている The Glasgow Haskell Compiler (GHC) や、便利な Haskell のライブラリのセットが付いてきます! </p> <p> <a href="http://www.haskell.org/platform/">http://www.haskell.org/platform/</a>にアクセスして、利用しているOS向けの指示に従ってください。 </p> </article> <article> <h3> GHCiの起動 </h3> <p> Terminal を開き、 <pre> $ ghci </pre> とタイプすることで、対話モードが起動できます。 </p> <p> 対話モードでは、実際に関数を呼び出して、結果を直接見ることができます。 </p> <p> 対話モードを終了するには、 <pre> ghci> :q </pre> とタイプし、ENTERを押します。 </p> </article> <article class="smaller"> <h3> GHCiで遊んでみる </h3> <p> <pre> ghci> 2+3 5 ghci> succ 3 4 ghci> 2 > 3 False ghci> True && False False </pre> gchi内で関数を定義する際はletが必要 <pre> 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 </pre> </p> </article> <article> <h3> なぜ Haskell で Web Serivce を書くのか </h3> <p> <ul> <li>RubyやPythonなどのインタプリタ言語と比較して高速です <li>高水準言語で、CやC++、Javaよりも自分の足を撃ち抜きにくいです </ul> </p> </article> <article> <h3> Warp </h3> <p> 軽量、高速 HTTP サーバです。 </p> <p> Haskell の軽量スレッドを活かして書かれています。 </p> <p> Pong benchmark (req/s) <img class='centered' src='pic/pong.png'> <a href="http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks">Preliminary Warp Cross-Language Benchmarks</a> </p> </article> <article> <h3> とりあえず、Warp 入れてみる </h3> <pre> $ cabal install warp </pre> <p> cabal を使えば簡単に入れられます。<br> cabal とは Haskell の Package 管理システムです。 </p> <p> インストールされているPackage、場所などは以下のコマンドで確かめられます。 </p> <pre> $ ghc-pkg list </pre> </article> <article> <h3> HaskellによるWeb Service </h3> <p> 今日の例題を見ていきます。<br> <a href="https://gist.github.com/amothic/5938617">https://gist.github.com/amothic/5938617</a> </p> <p> この例題は、URLによって出力する結果を変更するWeb Serviceです。 また、/welcome/worldへアクセスした場合、インクリメントされるcounterが表示されます。 </p> </article> <article class="smaller"> <h3> HaskellによるWeb Service </h3> <pre style="height:450px; overflow:scroll;"> {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types (status200, status404) import Network.Wai.Handler.Warp (run) import Control.Monad.Trans (lift) import Data.IORef (newIORef, atomicModifyIORef) import Data.ByteString.Lazy.UTF8 (fromString) application counter request = function counter where function = 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 _ = return $ responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index _ = return $ responseLBS status200 [("Content-type", "text/html")] $ "index page" hello _ = return $ responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom" world counter = do count <- lift $ incCount counter return $ responseLBS status200 [("Content-type", "text/html")] $ fromString $ show count incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) main = do counter <- newIORef 0 run 3000 $ application counter </pre> </article> <article class="smaller"> <h3> import </h3> <pre> {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types (status200, status404) import Network.Wai.Handler.Warp (run) import Control.Monad.Trans (lift) import Data.IORef (newIORef, atomicModifyIORef) import Data.ByteString.Lazy.UTF8 (fromString) $ ghci </pre> <p> 使用するmoduleをimportしています。 </p> <p> 括弧内に関数名を指定することでその関数のみをimportできます。 </p> <p> プログラムの最初に書かれている OverloadedStrings という言語拡張は、ダブルクオートで囲んだ文字列を、ByteString リテラルとして扱ってくれます。 ByteStringは、Stringと比較して効率よく文字列を扱います。 </p> </article> <article class="smaller"> <h3> 関数の型を確認する </h3> <p> Haskell は型を見れば多くのことが分かる言語です。<br> ということで、関数の型を見ていきます! </p> <p> 今回作成した例題をロードして、対話モードを開きます。 </p> <pre> $ ghci example.hs </pre> <p> 型を教えて貰うには、:t コマンドに続けて式を入力します。 </p> <pre> ghci> :t run run :: Port -> Application -> IO () </pre> </article> <article class="smaller"> <h3> run </h3> <p> まず、main内にあるrunから見ていきます。 </p> <pre> ghci> :t run run :: Port -> Application -> IO () </pre> <p> run は、Port と Application を受け取って、IO () を返す関数だということが分かります。 </p> <p> Haskellのすべての関数は、実は引数を1つだけ取ることになっています。( カリー化関数 ) </p> <p> 複数取るように見えますが、実際には関数が1つの引数で呼び出されると、その次の引数を受け取る関数を返します。 </p> <p> これにより、関数を本来より少ない引数で呼び出したときに部分適用された関数が得られます。 </p> </article> <article class="smaller"> <h3> IO () </h3> <pre> ghci> :t run run :: Port -> Application -> IO () </pre> <p> IO () は IO モナドを表しています。 <p> Haskell では、副作用を持つ処理は基本的に許されていません。 </p> <p> そのため、IO モナドという限られた範囲でのみ行えるようになっています。 </p> <p> IOモナドは外から触ることのできない抽象データ型です。 外から触ることを禁止することで参照透過性を保っています。 </p> <p> Port は、Int の別名です。<br> 別名などの定義は、:t ではみれないので、:i を使うとよいでしょう。<br> Haskellでは、関数は小文字、型名などは大文字で始まります。 </p> </article> <article class="smaller"> <h3> Application </h3> <pre> ghci> :i Application type Application = Request -> ResourceT IO Response </pre> <p> Request を受け取って Response を返す関数を表しています。 </p> <p> Portと同じようにtypeで定義されています。 </p> <p> これはRequest -> ResourceT IO Response に別名 Application をつけているという意味です。 </p> <p> Response は 2つのモナドに包まれています。 </p> <p> ResourceT は、IOのリソースの解放を安全に行うためのものです。 </p> </article> <article class="smaller"> <h3> run </h3> <p> 型情報から以下のことが分かります。 </p> <p> Port 番号と、Request を受け取って Response 返す関数を受け取る<br> run は IO () を返すので、外界に影響を与える </p> <p> 実際の動作としては、この関数 run は受け取った Port 3000番で、Application を実行します。 </p> <p> 次に、Request を受け取って Response を返す関数である application の実装を見ていきます。 </p> </article> <article class="smaller"> <h3> application </h3> <p> applicationでは、requestによって呼び出す関数を振り分けます。 where は関数内で使う変数を定義するもので、今回 function には呼び出す関数が入ります。 </p> <pre> application counter request = function counter where function = routes $ pathInfo request </pre> <p> routes の後ろに付いている $ は、関数適用演算子といって括弧の数を減らすのに役たちます。 普通の関数適用は非常に優先順位が高いのですが、$ は最も低い優先順位を持ちます。 </p> <pre> ($) :: (a -> b) -> a -> b f $ x = f x </pre> <p> 下記の2つのコードは同じ結果になります。 </p> <pre> sum (map sqrt [1..130]) sum $ map sqrt [1..130] </pre> </article> <article class="smaller"> <h3> request </h3> <p> request には、clientが送る様々な情報が含まれています。<br> その中には pathInfo という、どこの path へアクセスしてきたかの情報があります。 この情報をroutes関数に渡すことで呼び出す関数を振り分けています。 </p> <p> 以下はRequestに含まれる情報です。 <ul> <li>requestMethod :: Method <li>httpVersion :: HttpVersion <li>rawPathInfo :: ByteString <li>rawQueryString :: ByteString <li>serverName :: ByteString <li>serverPort :: Int <li>requestHeaders :: RequestHeaders <li>isSecure :: Bool <li>remoteHost :: SockAddr <li>pathInfo :: [Text] <li>queryString :: Query </ul> </p> </article> <article class="smaller"> <h3> request </h3> <p> requestは、data キーワードを使って定義されています。 Haskell では、data キーワードを使って自作のデータ型を作ることができます。 </p> <p> pathInfoという関数は、requestからpathに関する情報を抜き出しますが、これはレコード構文というものを利用しています。 </p> <p> 以下にレコード構文の例を表示します。 レコード構文を使うと簡単にアクセサ関数を定義できます。 </p> <pre style="height:300px; overflow:scroll;"> -- レコード構文を使わない場合 data Person = Person String String Int Float String String deriving (Show) firstName :: Person -> String firstName (Person firstname _ _ _ _ _) = firstname lastName :: Person -> String lastName (Person _ lastname _ _ _ _) = lastname age :: Person -> Int age (Person _ _ age _ _ _) = age ( 省略 ) -- レコード構文 data Person = Person { firstName :: String , lastName :: String , age::Int , height :: Float , phoneNumber :: String , flavor :: String } deriving (Show) </pre> </article> <article class="smaller"> <h3> routes </h3> <p> routes 関数では、routeSettingを付け足して、findRoute関数を呼び出しています。 </p> <pre> routes path = findRoute path routeSetting </pre> <p> routeSetting は、path と関数を記載した List になっています。 </p> <pre> routeSetting = [([], index), (["hello"], hello), (["welcome","world"],world)] </pre> <p> この情報を使って、返す関数を決めます。 </p> </article> <article class="smaller"> <h3> findRoute </h3> <pre> findRoute path [] = notFound findRoute path ((p,f):xs) | path == p = f | otherwise = findRoute path xs </pre> <p> findRoute は再帰的にListを探索しています。 </p> <p> 一致するものがなければ、notFound という関数を返します。<br> 一致するものがあれば、routeSetting に記載された関数を返します。 </p> <p> findRoute が二行にわたって書かれているのはパターンマッチを利用しているためです。 </p> <p> findRouteを呼ぶと、パターンが上から下の順で試されます。 渡された値が指定されたパターンと一致すると、対応する関数の本体が使われ、残りのパターンは無視されます。 </p> </article> <article class="smaller"> <h3> Response を返す関数の実装 </h3> <p> notFoundは、404 - File Not Found と表示する関数になります。 indexは、index page と表示する関数になります。 </p> <pre> notFound _ = return $ responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index _ = return $ responseLBS status200 [("Content-type", "text/html")] $ "index page" </pre> <p> responseLBS とは? </p> <pre> ghci> :t responseLBS responseLBS :: Status -> ResponseHeaders -> Data.ByteString.Lazy.Internal.ByteString -> Response </pre> <p> Statusと、ResponseHeaders、ByteStringを受け取り、Responseを返します。 </p> <p> 簡単に説明すると、文字列からResponseを構築するためのコンストラクターです。 </p> </article> <article> <h3> Counter の実装 </h3> <p> アクセスするたびに、Count がインクリメントされていくようなページを作ります。 </p> <p> ここでは、Thread-safe な State である Data.IORef を用います。 </p> </article> <article> <h3> Data.IORefの使い方 </h3> <p> IOモナドは中身に直接触ることはできません。 </p> <p> IOモナドからデータを手に入れる唯一の方法は <- を使うことです。<br> <- を使うことで、純粋なものと不純なものをきっちりと分けています。 </p> <p> 変更する際も、modifyIORefなどの関数を利用します。 </p> </article> <article> <h3> Data.IORefの使い方 </h3> <p> <pre> -- IORef Int という型のデータを作製する counter <- newIORef 0 -- データの更新を atomic に行う -- atomicModifyIORef には、更新したい IORef a 型の変数と、 -- IORef が持つ値を受け取って -- ( 更新後の値, 戻り値にしたい値 ) というタプルを返す関数を渡す incCount:: IORef a -> IO a incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) -- 現在のデータの値を受け取る currentNum <- readIORef counter </pre> </p> </article> <article class="smaller"> <h3> Data.IORef を使った Counter の実装 </h3> <p> まず、main内でcouterを初期化します。 </p> <pre> counter <- newIORef 0 </pre> <p> world 関数で、counterを利用します。 </p> <pre> world counter = do count <- lift $ incCount counter return $ responseLBS status200 [("Content-type", "text/html")] $ fromString $ show count incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) </pre> <p> incCountでは、atomicModifyIORefを使って、counterをインクリメントしています。 </p> <p> また、incCountを、ResourceTのモナド内に持ち込むためliftを行なっています。 </p> </article> <article class="smaller"> <h3> Haskell による Web Service </h3> <pre style="height:450px; overflow:scroll;"> {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types (status200, status404) import Network.Wai.Handler.Warp (run) import Control.Monad.Trans (lift) import Data.IORef (newIORef, atomicModifyIORef) import Data.ByteString.Lazy.UTF8 (fromString) application counter request = function counter where function = 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 _ = return $ responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index _ = return $ responseLBS status200 [("Content-type", "text/html")] $ "index page" hello _ = return $ responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom" world counter = do count <- lift $ incCount counter return $ responseLBS status200 [("Content-type", "text/html")] $ fromString $ show count incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) main = do counter <- newIORef 0 run 3000 $ application counter </pre> </article> <article> <h3> 実行方法 </h3> <p> 実行方法は2つあります。 </p> <p> <ul> <li>コンパイルせずに実行 </ul> <pre> $ runghc example.hs </pre> </p> <p> <ul> <li>コンパイルして実行 </ul> <pre> $ ghc --make example.hs $ ./example </pre> </p> </article> <article> <h3> 動作確認 </h3> <p> シンプルなシステムですが、実際に動くのか確かめてみます。 </p> <p> "index page" と表示されるはず<br> <a href="http://localhost:3000/">http://localhost:3000/</a> </p> <p> "hello, my name is Tom" と表示されるはず<br> <a href="http://localhost:3000/hello">http://localhost:3000/hello</a> </p> <p> インクリメントされる counter が表示されるはず<br> <a href="http://localhost:3000/welcome/world">http://localhost:3000/welcome/world</a> </p> <p> 一致するpathがないので、"404 - File Not Found" と表示されるはず<br> <a href="http://localhost:3000/hogehoge">http://localhost:3000/hogehoge</a> </p> </article> <article> <h3> Haskell プログラミング </h3> <p> ここまで書いてきて、Haskellのプログラムはだいぶ短く書けることに気がつくと思います。 </p> <p> 圧倒的な記述力も特徴のひとつです。 </p> <p> 速くて安全なHaskellで、あなたもWeb Serviceを作って見ませんか? </p> </article> <article> <h3> 参考文献 </h3> <p> <ul> <li>田中 英行, 村主 崇行(2012) 『すごいHaskellたのしく学ぼう!』 オーム社 <li><a href="http://mew.org/~kazu/material/2011-ll-haskell.pdf">メタプログラミングの光と闇 ~ Haskell 編~</a> <li><a href="http://www.yesodweb.com/blog/2011/03/preliminary-warp-cross-language-benchmarks">Preliminary Warp Cross-Language Benchmarks</a> <li><a href="http://yannesposito.com/Scratch/en/blog/Yesod-tutorial-for-newbies/">YBlog - Haskell web programming</a> </ul> </p> </article> </section> </body> </html>