Mercurial > hg > Members > toma > osc2013
view haskell.html @ 7:eea79db7cd9e
add record
author | Daichi TOMA <toma@cr.ie.u-ryukyu.ac.jp> |
---|---|
date | Sat, 06 Jul 2013 11:53:05 +0900 |
parents | 69e052c7ef6c |
children | 95fa8bea3364 |
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> </article> <article> <h3> なぜ Haskell で Web Serivce を書くのか </h3> <p> <ul> <li>RubyやPythonなどのインタプリタ言語と比較して高速です <li>高水準言語で、CやC++、Javaよりも自分の足を撃ち抜きにくいです </ul> </p> </article> <article> <h3> 命令型プログラミング言語との違い </h3> <p> 命令型プログラミング言語では、命令の並びをコンピュータに与えて、それを実行します。 </p> <p> 関数型プログラミング言語では、何であるかという定義を与えます。 </p> <p> 例:n番目のフィボナッチ数を求める関数の定義 <pre> fib 0 = 0 fib 1 = 1 fib n = fib (n - 1) + fib (n - 2) </pre> </p> </article> <article> <h3> Haskell の特徴 </h3> <p> <ul> <li>副作用を持たない </ul> </p> <p> Haskell は副作用を持ちません。 </p> <p> つまり、変数を書き換えることはできないということです。 </p> <p> 関数は同じ引数で呼ばれた場合、同じ値を返すことを保証します。 </p> <p> 関数の正しさを簡単に推察でき、正しいと分かっている関数同士を容易に組み合わせることができます。 </p> </article> <article> <h3> Haskell の特徴 </h3> <p> <ul> <li>遅延評価 </ul> </p> <p> Haskellは、結果が必要になるまで関数を評価しません。 </p> <p> 遅延評価のため、無限の大きさのデータを扱うことが可能です。 </p> <p> 例:奇数の無限のリストから最初の3つを入手する <pre> take 3 [1,3..] </pre> </p> </article> <article> <h3> Haskell の特徴 </h3> <p> <ul> <li>静的型付け </ul> </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> Haskell Platform をインストールしたら </h3> <p> Terminal を開き、 <pre> $ ghci </pre> とタイプすることで、対話モードが起動できます。 </p> <p> 対話モードでは、実際に関数を呼び出して、結果を直接見ることができます。 </p> <p> 対話モードを終了するには、 <pre> ghci> :q </pre> とタイプし、ENTERを押します。 </p> </article> <article> <h3> ghciで遊んでみる </h3> <p> <pre> ghci> 2+3 5 ghci> succ 3 4 ghci> 2 > 3 False ghci> True && False False </pre> </p> </article> <article> <h3> ghciで遊んでみる </h3> <p> 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> 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> </article> <article class="smaller"> <h3> 簡単なプログラム </h3> <p> <a href="https://gist.github.com/amothic/5938383">hello.hs</a> </p> <pre> {-# 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 </pre> <p> このソースコードを読み解いていきます。 </p> <p> Haskell は型を見れば多くのことが分かる言語です。<br> ということで、関数の型を見ていきます! </p> </article> <article class="smaller"> <h3> 関数の型を確認する </h3> <p> まず、対話モードを開きます。 </p> <pre> $ ghci </pre> <p> Wai 及び Warp の module を import します。<br> Wai というのは、Web Application Interface の略で、Web サーバと Web アプリケーション間の共通のプロトコルを取り扱います。 </p> <pre> ghci> :module +Network.Wai ghci> :module +Network.Wai.Handler.Warp </pre> <p> 型を教えて貰うには、:t コマンドに続けて式を入力します。 </p> <pre> ghci> :t run run :: Port -> Application -> IO () </pre> </article> <article class="smaller"> <h3> run </h3> <pre> ghci> :t run run :: Port -> Application -> IO () </pre> <p> run は、Port と Application を受け取って、IO () を返す関数だということが分かります。 </p> <p> IO () は IO モナドを表しています。 I/O といった副作用を持つ処理を行う時に利用します。 </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> Response は 2つのモナドに包まれています。 </p> <p> ResourceT は、IOのリソースの解放を安全に行うためのものです。 </p> <p> Haskell では、関数が副作用を持つことは許されませんが、IO モナドによって IO 操作を処理系に押し付けています。 </p> </article> <article class="smaller"> <h3> runとは結局なんぞや </h3> <pre> {-# 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 </pre> <p> 型情報から以下のことが分かります。 </p> <p> Port 番号と、Request を受け取って Response 返す関数を受け取る<br> run は IO () を返すので、外界に影響を与える </p> <p> 実際の動作としては、この関数 run は受け取った Port 3000番で、Application を実行します。 </p> <p> 次に、Application の実装を見ていきます。 </p> </article> <article class="smaller"> <h3> Hello Worldと出力する簡単なお仕事 </h3> <pre> application _ = return $ responseLBS status200 [("Content-Type", "text/plain")] "Hello World" </pre> <p> まず引数で渡される Request は _ (Underscore) となっているので、使用していません。 </p> <p> return の後ろに付いている $ は、関数適用演算子といって括弧の数を減らすのに役たちます。 普通の関数適用は非常に優先順位が高いのですが、$ は最も低い優先順位を持ちます。 </p> <p> 下記の2つのコードは同じ結果になります。 </p> <pre> sum (map sqrt [1..130]) sum $ map sqrt [1..130] </pre> </article> <article class="smaller"> <h3> responseLBS とは? </h3> <pre> ghci> :t responseLBS responseLBS :: Status -> ResponseHeaders -> Data.ByteString.Lazy.Internal.ByteString -> Response </pre> <p> Statusと、ResponseHeaders、ByteStringを受け取り、Responseを返します。 </p> <p> 簡単に説明すると、文字列からResponseを構築するためのコンストラクターです。 </p> <p> ByteStringは、Stringと比較して効率よく文字列を扱います。 プログラムの最初に書かれている OverloadedStrings という言語拡張は、ダブルクオートで囲んだ文字列を、ByteString リテラルとして扱ってくれます。 </p> <pre> {-# LANGUAGE OverloadedStrings #-} </pre> </article> <article class="smaller"> <h3> Hello Worldと出力するプログラム </h3> <p> <a href="https://gist.github.com/amothic/5938383">hello.hs</a> </p> <pre> {-# 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 </pre> </article> <article> <h3> おおよその内容は掴めた、では実行してみようではないか。 </h3> <p> 実行方法は2つあります。 </p> <p> <ul> <li>コンパイルせずに実行 </ul> <pre> $ runghc Hello.hs </pre> </p> <p> <ul> <li>コンパイルして実行 </ul> <pre> $ ghc --make Hello.hs $ ./Hello </pre> </p> </article> <article> <h3> サイトにアクセスしてみる </h3> <p> <a href="http://localhost:3000/">http://localhost:3000/</a> </p> <p> Hello Worldと表示されれば成功です。 </p> </article> <article> <h3> Routing </h3> <p> 次に単純な Routing を行うサイトを実装してみたいと思います。 </p> <p> http://localhost:3000/ の後の URL の Path によって 出力する結果を変更してみます。 </p> <p> Application が受け取る Request には、clientが送る様々な情報が含まれています。<br> その中には pathInfo という、どこの path へアクセスしてきたかの情報があります。 </p> </article> <article> <h3> Request に含まれる情報 </h3> <p> <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> アクセスしてきたURLで、出力結果を変えるプログラム </h3> <p> <a href="https://gist.github.com/amothic/5933808">routes.hs</a> <pre> 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 = responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index = responseLBS status200 [("Content-type", "text/html")] $ "index page" hello = responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom" world = responseLBS status200 [("Content-type", "text/html")] $ "Welcome to Underground" main = run 3000 application </pre> </p> </article> <article> <h3> pathInfoの情報をどう入手するか </h3> <p> 実は超簡単です。 </p> <pre> pathInfo request </pre> <p> pathInfo という関数に Request を渡すだけ! </p> <p> Haskell では、レコード構文というデータ型のフィールドにアクセスするためのコードを自動生成する仕組みがあります。 レコード構文で、データ型を定義するとデータ型が作られるのと同時に、フィールド名でフィールドを取得する関数たちが自動で作られます。 </p> <p> <pre> ghci> :t pathInfo pathInfo :: Request -> [Data.Text.Internal.Text] </pre> </p> </article> <article class="smaller"> <h3> レコード構文 </h3> <pre> -- レコード構文を使わない場合 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 height :: Person -> Float height (Person _ _ _ height _ _) = height phoneNumber :: Person -> String phoneNumber (Person _ _ _ _ number _) = number flavor :: Person -> String flavor (Person _ _ _ _ _ flavor) = flavor -- レコード構文 data Person = Person { firstName :: String , lastName :: String , age::Int , height :: Float , phoneNumber :: String , flavor :: String } deriving (Show) </pre> </p> </article> <article> <h3> pathによって返す関数を変えてみよう </h3> <p> まず、application で request を取り routes という関数に path を渡すように変更します。 </p> <pre> application request = return $ routes $ pathInfo request </pre> <p> routes は、path を受け取って response を返す関数です。 </p> <pre> routes path = findRoute path routeSetting </pre> <p> routes では、routeSetting という List から path が一致するもの探します。 </p> </article> <article> <h3> pathによって返す関数を変えてみよう </h3> <p> findRoute では、再帰的に List を探索しています。<br> 一致するものがなければ、notFound という関数を返します。<br> 一致するものがあれば、routeSetting に記載された関数を返します。 <pre> findRoute path [] = notFound findRoute path ((p,f):xs) | path == p = f | otherwise = findRoute path xs routeSetting = [([], index), (["hello"], hello), (["welcome","world"],world)] </pre> </article> <article class="smaller"> <h3> response関数を定義しよう </h3> <p> いくつか定義してみます。 </p> <pre> notFound = responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index = responseLBS status200 [("Content-type", "text/html")] $ "index page" hello = responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom" world = responseLBS status200 [("Content-type", "text/html")] $ "Welcome to Underground" </pre> </article> <article class="smaller"> <h3> 完成! </h3> <p> <a href="https://gist.github.com/amothic/5933808">routes.hs</a> <pre> 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 = responseLBS status404 [("Content-type", "text/html")] $ "404 - File Not Found" index = responseLBS status200 [("Content-type", "text/html")] $ "index page" hello = responseLBS status200 [("Content-type", "text/html")] $ "hello, my name is Tom" world = responseLBS status200 [("Content-type", "text/html")] $ "Welcome to Underground" main = run 3000 application </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> "welcome to Underground" と表示されるはず<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> Counter </h3> <p> 最後に、Counter の実装を行なってみたいと思います。 </p> <p> アクセスするたびに、Count がインクリメントされていくようなサイトを作成します。 </p> <p> ここでは、Thread-safe な State である Data.IORef を用います。 </p> </article> <article> <h3> 変数の更新はできなかったんじゃ? </h3> <p> Haskell では、変数の更新のような副作用を持つ処理は基本的に許されていません。 </p> <p> そのため、IO モナドという限られた範囲でのみ行えるようになっています。 </p> <p> IOモナドは中身に直接触ることのできない抽象データ型です。 状態を外から触ることを禁止することで参照透過性を保っています。 </p> <p> また、Haskell は遅延評価ですが、初期化などIOでは実行順序が重要になってきます。 モナドのbindを利用して、計算の実行順序を保証します。 </p> </article> <article> <h3> Data.IORefの使い方 </h3> <p> IOモナドは中身に直接触ることはできません。 </p> <p> IOモナドからデータを手に入れる唯一の方法は <- を使うことです。<br> <- を使うことで、純粋なものと不純なものをきっちりと分けています。 </p> <p> 変更する際も、modifyIORefなどの関数を利用します。 </p> </article> <article> <h3> Data.IORefの使い方 </h3> <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> <h3> では、このIORefを使ってCounterを作りましょう! </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> プログラム全容 </h3> <p> <a href="https://gist.github.com/amothic/5937576">counter.hs</a> <pre> {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types (status200) import Network.Wai.Handler.Warp (run) import Control.Monad.Trans (liftIO, lift) import Data.IORef (newIORef, atomicModifyIORef) import Data.ByteString.Lazy.UTF8 (fromString) application counter request = 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> </p> </article> <article> <h3> main </h3> <p> mainでは、counterの初期化を行なっています。 </p> <p> それをrunとdo構文で糊付けしています。 </p> <pre> main = do counter <- newIORef 0 run 3000 $ application counter </pre> </article> <article> <h3> application </h3> <p> applicationでは、引数をひとつ増やして、初期化されたIORefを受け取れるようにしています。 </p> <p> また、incCountを、ResourceTのモナド内に持ち込むためliftを行なっています。 </p> <p> incCountでは、atomicModifyIORefを使って、counterをインクリメントしています。 </p> <pre> application counter request = do count <- lift $ incCount counter return $ responseLBS status200 [("Content-type", "text/html")] $ fromString $ show count incCount counter = atomicModifyIORef counter (\c -> (c+1, c)) </pre> </article> <article class="smaller"> <h3> 完成! </h3> <p> <a href="https://gist.github.com/amothic/5937576">counter.hs</a> <pre> {-# LANGUAGE OverloadedStrings #-} import Network.Wai import Network.HTTP.Types (status200) import Network.Wai.Handler.Warp (run) import Control.Monad.Trans (liftIO, lift) import Data.IORef (newIORef, atomicModifyIORef) import Data.ByteString.Lazy.UTF8 (fromString) application counter request = 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> </p> </article> <article> <h3> 実行 </h3> <p> <a href="http://localhost:3000/">http://localhost:3000/</a> </p> <p> アクセスするたびに表示される数字がインクリメントされていくはずです。 </p> <p> Chromeだと、毎回faviconにアクセスされるせいで、countが+2されていきます… </p> </article> <article> <h3> そして気づくことなど </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>