[go: up one dir, main page]
More Web Proxy on the site http://driver.im/
SlideShare a Scribd company logo
ScalaプログラマのためのHaskell入門 
竹辺 靖昭
●GREEでHaskellプログラマをやっています http://labs.gree.jp/blog/2013/12/9201/ 
●最近Scalaでコードを書きはじめました 
自己紹介
●「Haskellは再代入ができないから参照透過性が確保される」 
●「Scalaを使っているけど、もっと関数的な書き方を身につけたいか らHaskellも勉強してみよう」 
●Haskellでも手続き的に書けます! 
Haskellに関するよくある誤解
●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に関するよくある誤解
●環境構築 
●Haskell Platformのインストール 
●Scala v.s. Haskell 
●Haskellコードの読み方 
●対応表 
●Haskellで書く手続き型プログラム 
●変数(MVar, STM) 
●配列(IOArray) 
●シェルスクリプト(Shelly) 
目次
●見た目はとてもかっこいい 
Haskellコードの読み方
●見た目はとてもかっこいい、が読み方がよくわからない 
Haskellコードの読み方
●関数定義 
●Haskellでは型を別の行に書く(省略も可) 
●引数に括弧がいらない 
Haskellコードの読み方 (続き) 
Scala 
Haskell 
def fun(x: Int, y: Int): Int = x + y 
fun :: Int -> Int -> Int fun x y = x + y
●ラムダ式 
●引数を使わない時は _ -> ... のように書く 
Haskellコードの読み方 (続き) 
Scala 
Haskell 
(x: Int) => x + 1 
x -> x + 1 
(x: Int, y: Int) => x + y 
x y -> x + y
●関数の後に引数を並べると関数適用 
Haskellコードの読み方 
Scala 
Haskell 
foo(1, 2) 
foo 1 2 
bar(2, x + 1) 
bar 2 (x + 1)
●引数を一部書かないと部分適用 
●最後からしか省略できないので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
●演算子の場合 
●括弧で囲むとinfixをprefixにできる 
●例: x + 1 は (x +) 1 と同じ 
●逆に普通の関数をバッククオートで囲むとinfixになる 
●例 isPrefixOf "hoge" str は "hoge" `isPrefixOf` str と同じ 
Haskellコードの読み方 (続き) 
Scala 
Haskell 
1 + (_: Int) 
(1 +) 
(_: Int) + (_: Int) 
(+)
●$演算子 
●後ろの式を括弧で囲んだのと同じ 
●baz $ foo $ 1 + 2 $ 3 + 4 は baz (foo (1 + 2) (3 + 4)) と同じ 
●変数名を装飾するものではない 
●かなり多用される 
Haskellコードの読み方 (続き)
●関数合成 
●関数合成もかなり多用される 
●ポイントフリースタイル 
●x -> f (g (h x)) は f . g . h と同じ 
●例: filter (not . even) [1, 2, 3] 
●'.' を使っているがメンバーやメソッドの参照ではない 
Haskellコードの読み方 (続き) 
Scala 
Haskell 
f compose g 
f . g
●関数合成の応用 
●(.) : 関数合成の前置記法 
●(.) f g は f . g と同じ 
●(f .) : 関数合成の部分適用 
Haskellコードの読み方 (続き)
●文字列から最初の単語を切り出す関数 takeWord = takeWhile (not . (flip elem) [' ', 'n', 't']) 
●takeWhile関数 
●takeWhile (文字からBoolの関数) (文字列) 文字列の文字が条件をみたす限り文字を取り続ける 
●elem関数 
●elem (文字) (文字のリスト) 文字が文字のリストに含まれていればTrue 
Haskellコードの読み方 (続き)
●"ポインティ"スタイル 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コードの読み方 (続き)
●対応表(モナド/逐次処理) 
●Haskellには文はない 
●文に相当する処理はモナドを使って書く 
Scala v.s. Haskell 
Scala 
Haskell 
文 
N/A 
for 
do 
map 
fmap 
flatMap 
>>= (bind) 
N/A 
>> (then)
●モナドといっても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
●対応表(制御構造) 
●Haskellのreturnは制御構造ではない 
●関数の途中で戻ることはできない 
Scala v.s. Haskell 
Scala 
Haskell 
if 
if 
match 
case 
for 
forM / forM_ 
while / do...while 
N/A 
return 
N/A
●繰り返しの例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
●Haskellのreturn 
●モナドでない値をモナドで使うためのもの 
●制御構造ではない 
●例getSmilingLine = do line <- getLine return $ line ++ " (^_^)" returnがたまたま最後の行にあるので値を "return"しているように見える 
Scala v.s. Haskell
●戻らない例 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
●こういう時はどう書くか? 
●再帰が一番無難な気がします 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
●対応表(変数) 
●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
●MVar 
●Control.Concurrent.MVarモジュールで定義されている 
●名前の通り並行用なので、複数スレッドで競合が発生したりしないようにできる 
●が、普通に書き換え可能な変数としても使える 
Haskellで書く手続き型プログラム
●MVarの操作 
●MVarの操作はIOモナドの中でできるようになっている 
●とりあえずはdoの中でおこなえばOK 
●初期化 
●var <- newMVar 初期値 
●読み出し 
●x <- readMVar var 
●更新 
●modifyMVar_ var (x -> 新しい値) 
●アトミックに書き換えられる 
●新しい値はIOモナドの値にしたいのでreturnを使って返す 
●古い値を使わない時はconstで定数関数にすればいい (_ -> ... でもOK) 
Haskellで書く手続き型プログラム
●例 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で書く手続き型プログラム
●例: 繰り返しでも使える 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で書く手続き型プログラム
●STM 
●Software Transactional Memory 
●Control.Concurrent.STMモジュールで定義されている 
●今回の話の範囲ではMVarとほぼ同じ 
●STMモナドの中でread、writeで変更ができるのでmodifyMVar_と比較するとよ り手続き的に書けるかも 
●並行プログラムで使う場合はロックの使い方が異なるので違いがでて くる 
●MVar: ロックを使用 
●STM: 楽観的ロック(ほぼロックフリー) 
Haskellで書く手続き型プログラム
●STMの操作 
●STMの操作はSTMモナドの中でできるようになっている 
●IOモナドの中でSTMの操作をおこなうには、STMモナドをIOモナドに変換する関 数(atomically)を使えばよい 
●初期化 
●var <- newTVarIO 初期値 
●読み出し 
●x <- atomically $ readTVar var 
●変更 
●atomically $ do val <- readTVar var -- 古い値が必要なければ読まなくてもいい writeTVar var (新しい値) 
Haskellで書く手続き型プログラム
●HaskellのArray 
●Data.Arrayモジュールで定義されている 
●Array型のMVarを使えば配列も手続き的に使えるのではないか? 
Haskellで書く手続き型プログラム
●Data.Arrayでは更新が非破壊的に行われる 
●1個の要素を更新するためにも配列全体をコピーして新しい配列を作る 
●更新が少ない配列やIOを使いたくないときには使える 
Haskellで書く手続き型プログラム
●IOArray 
●破壊的に書き込める配列 
●IOモナドの中で使用できる 
●初期化 
●arr <- newListArray インデックスの範囲 初期値 
●読み出し 
●x <- readArray arr インデックス 
●書込み 
●writeArray arr インデックス 新しい値 
Haskellで書く手続き型プログラム
●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で書く手続き型プログラム
●Shelly 
●シェルスクリプトを書くためのライブラリ 
●http://hackage.haskell.org/packages/archive/shelly/latest/doc/html/Shelly.html 
●使い方 
●shelly $ do (シェルスクリプト用の関数呼び出し) 
●run コマンド 引数のリスト 
●-|- : パイプ 
●lastExitCode 
●echo 文字列 
●... 
Haskellで書く手続き型プログラム
●ファイルの先頭に以下のようなおまじないを書く#!/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で書く手続き型プログラム
●shellyの後のdoの中はShモナド 
●IOモナドではないのでこのままではMVar等が使えない 
Haskellで書く手続き型プログラム
●こういう時はliftIOを使えばよい 
●多くの入出力ライブラリのモナドはIOモナドの上に積み上げて作られている 
●Shモナドは ReaderT (IORef State) IO a と同等 
●liftIOを使えばIOモナドで動く処理をこうしたモナドで動かすことができる 
Haskellで書く手続き型プログラム
●Haskellコードの読み方 
●Haskellで書く手続き型プログラム 
●変数(MVar, STM) 
●配列(IOArray) 
●シェルスクリプト(Shelly) 
●Haskellは手続き型プログラミング言語です 
まとめ

More Related Content

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