Workshop/StartHaskell2/exercise14

『もうちょっとだけモナド』 練習問題

Writerモナドを使ってみよう

逆ポーランド記法で書かれた式を計算する関数 solveRPN (p.214) に、Writerモナドを使って計算経過ログをとる機能を追加してみよう。

import Control.Monad.Writer

solveRPN :: String -> Double
solveRPN = head . foldl foldingFunction [] . words
  where
    foldingFunction (x:y:ys) "*" = (y * x) : ys
    foldingFunction (x:y:ys) "+" = (y + x) : ys
    foldingFunction (x:y:ys) "-" = (y - x) : ys
    foldingFunction xs       str = read str : xs

差分リスト

誰かよろしくお願いします


モナドとしての関数

関数のモナドの定義を見ながら、無理やり使える例を探してみよう。

instance Monad ((-> r) where
  return x = \_ -> x
  h >>= f = \w -> f (h w) w

たとえば、リストの前半分だけ取る関数 takeHalf は、こんな風に書ける。(あまり書こうとは思わないけど)

import Control.Monad.Instances

takeHalf :: [a] -> [a]
takeHalf = (`div`2).length >>= take

Readerモナドを使ってみよう

いまUnixの環境変数を、変数名と値の連想リストで表すことにする。

type Env = [(String, String)]

environment :: Env
environment = 
  [ ("HOME", "/home/haskell")
  , ("PATH", "/bin:/usr/bin")
  , ("LANG", "ja_JP.UTF-8")
  ]

Control.Monad.Readerで定義されている Reader モナドでは、 ask 関数を使って、環境を参照することができる。

環境変数から指定された変数名の値を探してくる関数 lookupEnv を実装しよう。ただし、変数名が見つからない場合は、空文字を返す。

import Control.Monad.Reader

lookupEnv :: String -> Reader Env String
lookupEnv key = do
  env <- ask
  ...

test = runReader (lookupEnv "LANG") environment

また、$HOME が設定されていればその値を返し、なければ “/etc” を返す関数 getConfPath を書いてみよう。

import Control.Monad.Reader

getConfPath :: Reader Env String

Stateモナドを使ってみよう

2次元平面上を移動する処理を書いてみよう。(上下左右の座標実装はお任せ)

import Control.Monad.State

type Point = (Int, Int)

move :: Char -> State Point ()
move 'u' =  -- 上へ移動
move 'd' =  -- 下へ移動
move 'l' =  -- 左へ移動
move 'r' =  -- 右へ移動

moveGame :: String -> State Point ()
moveGame []     =
moveGame (c:cs) =

main :: IO ()
main = print $ evalState (moveGame "ulrdddlr") (0,0)

スタックを拡張しよう

(p.333) で出てきたスタックに、以下の操作を追加しよう。

  • undo : 直前の操作を取り消し、状態を元に戻す
  • clear : スタックを空にする。ただし操作履歴の情報は残る(undoできるようにしておく)
type Stack =  -- 新しいスタックの定義

pop :: Stack -> State Stack Int

push :: Int -> Stack -> State Stack ()

undo :: Stack -> State Stack ()

clear :: Stack -> State Stack ()

実行例

ghci> runState (push 1 >> clear) [0]
((), [])
ghci> runState (push 1 >> clear >> undo) [0]
((), [1, 0])
ghci> runState (pop >> undo) [0]
((), [0])

Eitherモナド

(p.342 NOTE) ピエールのバランス棒に鳥がとまる様子をシミュレートしました。ピエールが滑って落ちた瞬間に棒の左右に何羽の鳥が止まってるか分かるように、Eitherモナドを使って書き直してみよう。

import Control.Monad.Error

type Birds = Int
type Pole  = (Birds, Birds)

landLeft :: Birds -> Pole -> Maybe Pole
landLeft n (left, right)
  | abs ((left + n) - right) < 4 = Just (left + n, right)
  | otherwise                    = Nothing

landRight :: Birds -> Pole -> Maybe Pole
landRight n (left, right)
  | abs (left - (right + n)) < 4 = Just (left, right + n)
  | otherwise                    = Nothing

banana :: Pole -> Maybe Pole
banana _ = Nothing

routine :: Maybe Pole
routine = do
  start  <- return (0,0)
  first  <- landLeft 2 start
  second <- landRight 2 first
  landLeft 1 second

Prob(確率)モナド

(p.361) 結果が一致する事象の確率をまとめる関数 joinSameOutcomes を書いてみよう。

import Data.Ratio

newtype Prob a = Prob { getProb :: [(a, Rational)] } deriving Show

instance Functor Prob where
  fmap f (Prob xs) Prob $ map (\(x, p) -> (f x, p)) xs

instance Monad Prob where
  return x = Prob [(x, 1%1)]
  m >>= f = flatten (fmap f m)
  fail _ = Prob []

flatten :: Prob (Prob a) -> Prob a
flatten (Prob xs) = Prob $ concat $ map multAll xs
  where multAll (Prob innerxs, p) = map (\(x, r) -> (x, p*r)) innerxs

joinSameOutcomes :: Eq a => Prob a -> Prob a
joinSameOutcomes = ....