ScalaプログラマのためのHaskell入門

4,855 views

Published on

Scala Matsuriで話したScalaプログラマのためのHaskell入門のスライドです。

Published in: Software

ScalaプログラマのためのHaskell入門

  1. 1. ScalaプログラマのためのHaskell入門 竹辺 靖昭
  2. 2. ●GREEでHaskellプログラマをやっています http://labs.gree.jp/blog/2013/12/9201/ ●最近Scalaでコードを書きはじめました 自己紹介
  3. 3. ●「Haskellは再代入ができないから参照透過性が確保される」 ●「Scalaを使っているけど、もっと関数的な書き方を身につけたいか らHaskellも勉強してみよう」 ●Haskellでも手続き的に書けます! Haskellに関するよくある誤解
  4. 4. ●Haskellで手続き的に書くなんて邪道ではないか? ●Haskellの設計者 Simon Peyton Jones の言 ●Tackling the Awkward Squad: monadic input/output, concurrency, exceptions, and foreign-language calls in Haskell ●Haskell is the world’s finest imperative programming language 「Haskellは世界最高の手続き型プログラミング言語です」 ●設計者が手続き型言語と言っているので手続き的に書いて全く問題あ りません Haskellに関するよくある誤解
  5. 5. ●環境構築 ●Haskell Platformのインストール ●Scala v.s. Haskell ●Haskellコードの読み方 ●対応表 ●Haskellで書く手続き型プログラム ●変数(MVar, STM) ●配列(IOArray) ●シェルスクリプト(Shelly) 目次
  6. 6. ●見た目はとてもかっこいい Haskellコードの読み方
  7. 7. ●見た目はとてもかっこいい、が読み方がよくわからない Haskellコードの読み方
  8. 8. ●関数定義 ●Haskellでは型を別の行に書く(省略も可) ●引数に括弧がいらない Haskellコードの読み方 (続き) Scala Haskell def fun(x: Int, y: Int): Int = x + y fun :: Int -> Int -> Int fun x y = x + y
  9. 9. ●ラムダ式 ●引数を使わない時は _ -> ... のように書く Haskellコードの読み方 (続き) Scala Haskell (x: Int) => x + 1 x -> x + 1 (x: Int, y: Int) => x + y x y -> x + y
  10. 10. ●関数の後に引数を並べると関数適用 Haskellコードの読み方 Scala Haskell foo(1, 2) foo 1 2 bar(2, x + 1) bar 2 (x + 1)
  11. 11. ●引数を一部書かないと部分適用 ●最後からしか省略できないのでHaskellでは引数の順番が重要 ●flip関数: flip f = x y -> f y x ●x -> foo x 2 は (flip f) 2 と書ける Haskellコードの読み方 (続き) Scala Haskell foo(1, (_: Int)) foo 1 bar((_: Int), (_: Int)) bar
  12. 12. ●演算子の場合 ●括弧で囲むとinfixをprefixにできる ●例: x + 1 は (x +) 1 と同じ ●逆に普通の関数をバッククオートで囲むとinfixになる ●例 isPrefixOf "hoge" str は "hoge" `isPrefixOf` str と同じ Haskellコードの読み方 (続き) Scala Haskell 1 + (_: Int) (1 +) (_: Int) + (_: Int) (+)
  13. 13. ●$演算子 ●後ろの式を括弧で囲んだのと同じ ●baz $ foo $ 1 + 2 $ 3 + 4 は baz (foo (1 + 2) (3 + 4)) と同じ ●変数名を装飾するものではない ●かなり多用される Haskellコードの読み方 (続き)
  14. 14. ●関数合成 ●関数合成もかなり多用される ●ポイントフリースタイル ●x -> f (g (h x)) は f . g . h と同じ ●例: filter (not . even) [1, 2, 3] ●'.' を使っているがメンバーやメソッドの参照ではない Haskellコードの読み方 (続き) Scala Haskell f compose g f . g
  15. 15. ●関数合成の応用 ●(.) : 関数合成の前置記法 ●(.) f g は f . g と同じ ●(f .) : 関数合成の部分適用 Haskellコードの読み方 (続き)
  16. 16. ●文字列から最初の単語を切り出す関数 takeWord = takeWhile (not . (flip elem) [' ', 'n', 't']) ●takeWhile関数 ●takeWhile (文字からBoolの関数) (文字列) 文字列の文字が条件をみたす限り文字を取り続ける ●elem関数 ●elem (文字) (文字のリスト) 文字が文字のリストに含まれていればTrue Haskellコードの読み方 (続き)
  17. 17. ●"ポインティ"スタイル takeWord str = takeWhile (c -> not (elem c [' ', 'n', 't'])) str ●最後のstrは同じなのでなくてもよい takeWord = takeWhile (c -> not (elem c [' ', 'n', 't'])) ●cを消したいのでflipを使う takeWord = takeWhile (c -> not ((flip elem) [' ', 'n', 't'] c)) ●x -> g (f x) は g . f と同じ takeWord = takeWhile (not . (flip elem) [' ', 'n', 't']) Haskellコードの読み方 (続き)
  18. 18. ●対応表(モナド/逐次処理) ●Haskellには文はない ●文に相当する処理はモナドを使って書く Scala v.s. Haskell Scala Haskell 文 N/A for do map fmap flatMap >>= (bind) N/A >> (then)
  19. 19. ●モナドといってもdo記法の中では「文」を並べているように書けるの であまり違和感はない ●例:main :: IO () main = do let prompt = "Input your name: " let message = "Hello, " putStr prompt name <- getLine putStrLn $ message ++ name ●実際にはdo記法は >> と >>= を使った式に展開できる (cf. Real World Haskell 14.11) Scala v.s. Haskell
  20. 20. ●対応表(制御構造) ●Haskellのreturnは制御構造ではない ●関数の途中で戻ることはできない Scala v.s. Haskell Scala Haskell if if match case for forM / forM_ while / do...while N/A return N/A
  21. 21. ●繰り返しの例import Control.Monad -- forMを定義しているモジュール main :: IO () main = do let prompt = "Input your name: " let message = "Hello, " forM_ [1..5] $ i -> do putStr prompt name <- getLine putStrLn $ message ++ name Scala v.s. Haskell
  22. 22. ●Haskellのreturn ●モナドでない値をモナドで使うためのもの ●制御構造ではない ●例getSmilingLine = do line <- getLine return $ line ++ " (^_^)" returnがたまたま最後の行にあるので値を "return"しているように見える Scala v.s. Haskell
  23. 23. ●戻らない例 import Control.Monad main :: IO () main = do let prompt = "Input your name: " let message = "Hello, " forM_ [1..5] $ i -> do putStr prompt name <- getLine if name /= "" then do putStrLn $ message ++ name return () else putStrLn "Empty name is not allowed." Scala v.s. Haskell
  24. 24. ●こういう時はどう書くか? ●再帰が一番無難な気がします loop :: IO () loop = do let prompt = "Input your name: " let message = "Hello, " putStr prompt name <- getLine if name /= "" then putStrLn $ message ++ name else do putStrLn "Empty name is not allowed." loop main :: IO () main = loop Scala v.s. Haskell
  25. 25. ●対応表(変数) ●Haskellでも再代入できる Scala v.s. Haskell Scala Haskell val x = 1 x = 1 do記法の中では let x = 1 def fun = ... fun = ... var x = 1 varX <- newMVar 1 x = 2 (再代入) modifyMVar_ varX (const $ return 2) x (varの参照) x <- readMVar varX
  26. 26. ●MVar ●Control.Concurrent.MVarモジュールで定義されている ●名前の通り並行用なので、複数スレッドで競合が発生したりしないようにできる ●が、普通に書き換え可能な変数としても使える Haskellで書く手続き型プログラム
  27. 27. ●MVarの操作 ●MVarの操作はIOモナドの中でできるようになっている ●とりあえずはdoの中でおこなえばOK ●初期化 ●var <- newMVar 初期値 ●読み出し ●x <- readMVar var ●更新 ●modifyMVar_ var (x -> 新しい値) ●アトミックに書き換えられる ●新しい値はIOモナドの値にしたいのでreturnを使って返す ●古い値を使わない時はconstで定数関数にすればいい (_ -> ... でもOK) Haskellで書く手続き型プログラム
  28. 28. ●例 import Control.Concurrent.MVar main :: IO () main = do x <- newMVar 0 val <- readMVar x putStrLn $ "x = " ++ (show val) modifyMVar_ x (const $ return (val + 1)) val <- readMVar x putStrLn $ "x = " ++ (show val) Haskellで書く手続き型プログラム
  29. 29. ●例: 繰り返しでも使える import Control.Monad import Control.Concurrent.MVar main :: IO () main = do varPrompt <- newMVar "Input your name: " -- Stringでも使える let message = "Hello, " forM_ [1..5] $ i -> do prompt <- readMVar varPrompt putStr prompt name <- getLine putStrLn $ message ++ name modifyMVar_ varPrompt (const $ return "Input your name again: ") Haskellで書く手続き型プログラム
  30. 30. ●STM ●Software Transactional Memory ●Control.Concurrent.STMモジュールで定義されている ●今回の話の範囲ではMVarとほぼ同じ ●STMモナドの中でread、writeで変更ができるのでmodifyMVar_と比較するとよ り手続き的に書けるかも ●並行プログラムで使う場合はロックの使い方が異なるので違いがでて くる ●MVar: ロックを使用 ●STM: 楽観的ロック(ほぼロックフリー) Haskellで書く手続き型プログラム
  31. 31. ●STMの操作 ●STMの操作はSTMモナドの中でできるようになっている ●IOモナドの中でSTMの操作をおこなうには、STMモナドをIOモナドに変換する関 数(atomically)を使えばよい ●初期化 ●var <- newTVarIO 初期値 ●読み出し ●x <- atomically $ readTVar var ●変更 ●atomically $ do val <- readTVar var -- 古い値が必要なければ読まなくてもいい writeTVar var (新しい値) Haskellで書く手続き型プログラム
  32. 32. ●HaskellのArray ●Data.Arrayモジュールで定義されている ●Array型のMVarを使えば配列も手続き的に使えるのではないか? Haskellで書く手続き型プログラム
  33. 33. ●Data.Arrayでは更新が非破壊的に行われる ●1個の要素を更新するためにも配列全体をコピーして新しい配列を作る ●更新が少ない配列やIOを使いたくないときには使える Haskellで書く手続き型プログラム
  34. 34. ●IOArray ●破壊的に書き込める配列 ●IOモナドの中で使用できる ●初期化 ●arr <- newListArray インデックスの範囲 初期値 ●読み出し ●x <- readArray arr インデックス ●書込み ●writeArray arr インデックス 新しい値 Haskellで書く手続き型プログラム
  35. 35. ●STArray ●破壊的に書き込める配列 ●STモナドの中で使用できる ●使い方はIOArrayとほぼ同じだが、runSTArrayという関数を使うとData.Arrayに 戻せるのでIOにしたくないときに使える ●使い方 ●runSTArray (STArrayの操作) ●stSortArray arr = runSTArray $ do stArr <- thaw arr -- Data.ArrayをSTArrayに変換 (thaw: 解凍) ioSort stArr return stArr -- STArrayをreturnするとData.Arrayに変換される Haskellで書く手続き型プログラム
  36. 36. ●Shelly ●シェルスクリプトを書くためのライブラリ ●http://hackage.haskell.org/packages/archive/shelly/latest/doc/html/Shelly.html ●使い方 ●shelly $ do (シェルスクリプト用の関数呼び出し) ●run コマンド 引数のリスト ●-|- : パイプ ●lastExitCode ●echo 文字列 ●... Haskellで書く手続き型プログラム
  37. 37. ●ファイルの先頭に以下のようなおまじないを書く#!/usr/bin/env runhaskell {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE ExtendedDefaultRules #-} {-# OPTIONS_GHC -fno-warn-type-defaults #-} import Shelly import qualified Data.Text.Lazy as LT default (LT.Text) Haskellで書く手続き型プログラム
  38. 38. ●shellyの後のdoの中はShモナド ●IOモナドではないのでこのままではMVar等が使えない Haskellで書く手続き型プログラム
  39. 39. ●こういう時はliftIOを使えばよい ●多くの入出力ライブラリのモナドはIOモナドの上に積み上げて作られている ●Shモナドは ReaderT (IORef State) IO a と同等 ●liftIOを使えばIOモナドで動く処理をこうしたモナドで動かすことができる Haskellで書く手続き型プログラム
  40. 40. ●Haskellコードの読み方 ●Haskellで書く手続き型プログラム ●変数(MVar, STM) ●配列(IOArray) ●シェルスクリプト(Shelly) ●Haskellは手続き型プログラミング言語です まとめ

×