準備
uninstall-hs で古いの消して、 http://www.haskell.org/platform/mac.html から新しいの入れて、cabal install ghc-mod した。
目標
以下のコードと同様のHaskellコードを書く。
class Temp {
public static void main(String[] args){
Optional<Integer> redbull = Optional.of(200);
Optional<Integer> water = Optional.empty();
// Optional<Integer> water = Optional.of(140);
Optional<Integer> sandwich = Optional.of(250);
List<Optional<Integer>> shoppingList = Arrays.asList(redbull, sandwich, water);
Optional<Integer> total = shoppingList.stream().reduce(
Optional.of(0), // 最初は0円からカウントしていく
(p, c) -> p.flatMap(v1 -> c.flatMap(v2 -> Optional.of(v1 + v2)))
// 型注釈をつけるとこんな意味
// (Optional<Integer> p, Optional<Integer> c) -> p.flatMap((Integer v1) -> c.flatMap((Integer v2) -> Optional.of(v1 + v2)))
);
if (total.isPresent()) {
System.out.println("全部買えました。合計" + total.get() + "円でした");
} else {
System.out.println("何かが売り切れていたので何も買ってきませんでした…");
}
}
}
作業
モナドわすれた
do x <- mx
y <- my
return x + y
的なの考えた時に
mx >>= (\x -> my >>= (\y -> return $ x + y))
と等価だよと教えてもらった。ひびのさんめっちゃ優しい。
まずは型から考える
notogawa さんが第一回の後に書いてくれた通りにやる。
shoppingList :: [Maybe Integer]
shoppingList = [Just 200, Nothing, Just 250]
これを、Maybe Integer に変換できれば終わり。
つまり [Maybe Integer] -> Maybe Integer にしたい。
調べるのはHoogle使う。
たぶん、foldl とか使う?
Just 0 & [Just 200, Nothing, Just 250] → Just 200 & [Nothing, Just 250] → Nothing & [Just 250] → Nothing []
と畳み込んでいけばなんとかなるに違いない。(Java8の書き方と同じ)
[Maybe Integer] -> Maybe Integer に変換する関数ないかなーと [Maybe a] -> Maybe a で調べる。msum というのを見つける。
MonadPlus m => [m a] -> m a なので、それっぽい。しかしMonadPlus のMaybeの実装を見るとそれっぽくない。
instance MonadPlus Maybe where
mzero = Nothing
Nothing `mplus` ys = ys
xs `mplus` _ys = xs
コレじゃないなー。となんとなく察して途方にくれる。
sequence で Monad m => [m a] -> m [a] に出来るよとひびのさんに教えてもらった。
次は Monad m => m [a] -> m a が欲しいなーと思った。
m [a] -> m a で調べてもなんかそれっぽいのがない。
tanakhさんから m [a] -> m a は m 外すと [a] -> a になるので、こういうのはmがFunctorなので持ち上げるためにはfmapが定番、と教わる。
[a] -> a はsumらしい。
なので fmap sum . sequence とすると [m a] -> m a で当初の [Maybe Integer] -> Maybe Integer にマッチするようになった。
fmap sum . sequence は (fmap sum) . (sequence) という結合順序。
(.) の型は (b -> c) -> (a -> b) -> (a -> c) 、つまり関数を2つとって関数を返す関数だ。
つまり、
a は [m a]
c は m a
なので、b -> c 、[m a] -> m [a] は既に知っているので、 m [a] -> m a が用意できれば a -> c の関数に合成できる。
b は m [a] が当てはまるので、 b -> c は m [a] -> m a だ。
(.) (fmap sum) sequence を当てはめると
この時
sum は [a] -> a で fmapが (a -> b) -> f a -> f b なので
Monad f => ([a] -> a) -> f [a] -> f a と考えると型があう
つまり、a → b → c の b は関数を部分適用して得られた関数だ。
(ここの文章を後から読むとHaskellの難しい本に書いてある説明と同じようなものに見えてくるので文章で記述するの難しいんだなと悟る。)
まとめると Monad m => [m a] -> m a
fmap sum . sequence
Java8 と同じ結果にしてみる
main :: IO ()
main = putStrLn $ resultText $ calc [Just 200, Nothing, Just 250]
resultText :: Show a => Maybe a -> String
resultText (Just n) = "全部買えました。合計" ++ (show n) ++ "円でした"
resultText Nothing = "何かが売り切れていたので何も買ってきませんでした…"
calc :: [Maybe Integer] -> Maybe Integer
calc = fmap sum . sequence
わー、解法が全然違う。
どこでこの差がついたし…。Javaで同じ書き方ができる気がしないぞ!
Java8 と同じやり方にしてみる
main :: IO ()
main = putStrLn $ resultText $ calc' [Just 200, Nothing, Just 250]
resultText :: Show a => Maybe a -> String
resultText (Just n) = "全部買えました。合計" ++ (show n) ++ "円でした"
resultText Nothing = "何かが売り切れていたので何も買ってきませんでした…"
calc' :: [Maybe Integer] -> Maybe Integer
calc' = foldr (\p c -> p >>= (\v1 -> c >>= (\v2 -> return $ v1 + v2))) (Just 0)
Java8 と同じにしてみた。foldr が reduce flatMap が >>= に対応しているだけで全く同じ構造だ。
main :: IO ()
main = putStrLn $ resultText $ calc'' [Just 200, Nothing, Just 250]
resultText :: Show a => Maybe a -> String
resultText (Just n) = "全部買えました。合計" ++ (show n) ++ "円でした"
resultText Nothing = "何かが売り切れていたので何も買ってきませんでした…"
calc'' :: [Maybe Integer] -> Maybe Integer
calc'' = foldr (\p c -> do { v1 <- p ; v2 <- c ; return $ v1 + v2}) (Just 0)
do式使ったほうがやっぱりわかりやすい。
まとめ
ひびのさんが僕の理解した所によると以下のようなことを言ってたのが面白かった。
「2重のレイヤーの構造に対して内側に対する演算とか定義できるのはHaskellのクールなところだよねー。」
モナド則を満たしていればモナドというのは、筒の底に穴が開いていなければ箱、というのと同等なんじゃないかな、と思った。
Java8 と Haskell の違いよくわからない。
Java8にreturnがあればdo式のないHaskellと同じ表現力になるのかなー、と思ったけど型推論が全くアレなのでreturnがどこ型のreturnかわかんないしダメだこりゃ。