第1章 導入
第2章 はじめの一歩
第3章 型と型クラス
第4章 関数定義
第5章 リスト内包表記
- Q: すべての答えがないのはなぜだろう
- A: 多分大学の課題で出してるから
第6章 再帰関数
第7章 高階関数
第8章 型と型クラスの定義
恒真式検査器
type Bit = Int int2bin :: Int -> [Bit] int2bin 0 = [] int2bin n = n `mod` 2 : int2bin (n `div` 2) rmdups :: (Eq a) => [a] -> [a] rmdups [] = [] rmdups (x : xs) = x : rmdups (filter (/= x) xs) type Assoc k v = [(k, v)] find :: (Eq a) => a -> Assoc a b -> b find a ((a', b) : xs) | a == a' = b | otherwise = find a xs data Prop = Const Bool | Var Char | Not Prop | And Prop Prop | Imply Prop Prop p1 :: Prop p1 = And (Var 'A') (Not (Var 'A')) p2 :: Prop p2 = Imply (And (Var 'A') (Var 'B')) (Var 'B') p3 :: Prop p3 = Imply (Var 'A') (And (Var 'A') (Var 'B')) p4 :: Prop p4 = Imply (And (Var 'A') (Imply (Var 'A') (Var 'B'))) (Var 'B') type Subst = Assoc Char Bool eval :: Subst -> Prop -> Bool eval _ (Const b) = b eval s (Var x) = find x s eval s (Not p) = not $ eval s p eval s (And p q) = eval s p && eval s q eval s (Imply p q) = not (eval s p) || eval s q vars :: Prop -> [Char] vars (Const _) = [] vars (Var x) = [x] vars (Not p) = vars p vars (And p q) = vars p ++ vars q vars (Imply p q) = vars p ++ vars q bools :: Int -> [[Bool]] bools 0 = [[]] bools n = map (False :) bss ++ map (True :) bss where bss = bools (n - 1) substs :: Prop -> [Subst] substs p = map (zip vs) $ bools $ length vs where vs = rmdups $ vars p isTaut :: Prop -> Bool isTaut p = and [eval s p | s <- substs p]
抽象機械
data Expr = Val Int | Add Expr Expr data Op = EVAL Expr | ADD Int type Cont = [Op] eval :: Expr -> Cont -> Int eval (Val n) c = exec c n eval (Add x y) c = eval x $ EVAL y : c exec :: Cont -> Int -> Int exec [] n = n exec (EVAL y : c) n = eval y (ADD n : c) exec (ADD n : c) m = exec c (n + m) value :: Expr -> Int value e = eval e []
第9章 カウントダウン問題
- ちょっと駆け足な感じが
第10章 対話プログラム
副作用があるプログラムを「ある状態の世界」を引数に取り、「別の状態の世界」を結果として返す純粋な関数とみなす
World -> World
ということ- 良いコトバチカラだ(小並感)
「いったに汚れたら永遠に汚れたままであり、純粋さを取り戻せる可能性はありません!」
- もうちょっとマイルドな表現なかったんか;;
ハングマン
import System.IO (hSetEcho, stdin) hangman :: IO () hangman = do putStrLn "Think of a word:" word <- sgetLine putStrLn "Try to guess it:" play word sgetLine :: IO String sgetLine = do x <- getCh if x == '\n' then do putChar x return [] else do putChar '-' xs <- sgetLine return (x : xs) getCh :: IO Char getCh = do hSetEcho stdin False x <- getChar hSetEcho stdin True return x play :: String -> IO () play word = do putStr "? " guess <- getLine if guess == word then putStrLn "You got it!!" else do putStrLn (match word guess) play word match :: String -> String -> String match xs ys = do x <- xs if x `elem` ys then [x] else "-"
ニム
import Data.Char (digitToInt, isDigit) next :: Int -> Int next 1 = 2 next 2 = 1 type Board = [Int] initial :: Board initial = [5, 4, 3, 2, 1] finished :: Board -> Bool finished = all (== 0) valid :: Board -> Int -> Int -> Bool valid board row num = (board !! (row - 1)) >= num move :: Board -> Int -> Int -> Board move board row num = take (row - 1) board ++ [board !! (row - 1) - num] ++ drop row board putRow :: Int -> Int -> IO () putRow row num = do putStr $ show row putStr ":" putStrLn $ concat $ replicate num " *" putBoard :: Board -> IO () putBoard [a, b, c, d, e] = do putRow 1 a putRow 2 b putRow 3 c putRow 4 d putRow 5 e getDigit :: String -> IO Int getDigit prompt = do putStr prompt x <- getChar newline if isDigit x then return (digitToInt x) else do putStrLn "ERROR: Invalid digit" getDigit prompt newline :: IO () newline = putStrLn "" play :: Board -> Int -> IO () play board player = do newline putBoard board if finished board then do newline putStr "Player " putStr $ show $ next player putStr " wins!!" else do newline putStr "Player " print player row <- getDigit "Enter a row number: " num <- getDigit "Stars to remove : " if valid board row num then play (move board row num) (next player) else do newline putStrLn "ERROR: Invalid move" play board player newline nim :: IO () nim = play initial 1
ライフゲーム
cls :: IO () cls = putStr "\ESC[2J" wait :: Int -> IO () wait n = sequence_ [return () | _ <- [1 .. n]] type Pos = (Int, Int) writeAt :: Pos -> String -> IO () writeAt p s = do goto p putStr s goto :: Pos -> IO () goto (x, y) = putStr ("\ESC[" ++ show y ++ ";" ++ show x ++ "H") width :: Int width = 10 height :: Int height = 10 type Board = [Pos] showCells :: Board -> IO () showCells b = sequence_ [writeAt p "0" | p <- b] isAlive :: Board -> Pos -> Bool isAlive b p = p `elem` b isEmpty :: Board -> Pos -> Bool isEmpty b p = not (isAlive b p) neighbs :: Pos -> [Pos] neighbs (x, y) = map wrap [ (x - 1, y - 1), (x, y - 1), (x + 1, y - 1), (x - 1, y), (x + 1, y), (x - 1, y + 1), (x, y + 1), (x + 1, y + 1) ] wrap :: Pos -> Pos wrap (x, y) = ((x - 1 `mod` width) + 1, (y - 1 `mod` height) + 1) liveNeighbs :: Board -> Pos -> Int liveNeighbs b = length . filter (isAlive b) . neighbs survivors :: Board -> [Pos] survivors b = [p | p <- b, liveNeighbs b p `elem` [2, 3]] births :: Board -> [Pos] births b = [p | p <- rmdups $ concatMap neighbs b, isEmpty b p, liveNeighbs b p == 3] rmdups :: (Eq a) => [a] -> [a] rmdups [] = [] rmdups (x : xs) = x : rmdups (filter (/= x) xs) nextgen :: Board -> Board nextgen b = survivors b ++ births b life :: Board -> IO () life b = do cls showCells b wait 500000 life $ nextgen b initial :: Board initial = [(4, 2), (2, 3), (4, 3), (3, 4), (4, 4)]
第11章 負けない三目並べ
三目並べ
import Data.Char (isDigit) import Data.List (transpose) import GHC.IO.Handle (BufferMode (NoBuffering), hSetBuffering) import System.IO (stdout) cls :: IO () cls = putStr "\ESC[2J" goto :: (Int, Int) -> IO () goto (x, y) = putStr $ "\ESC[" ++ show x ++ ";" ++ show y ++ "H" size :: Int size = 3 data Player = O | B | X deriving (Eq, Ord, Show) type Grid = [[Player]] next :: Player -> Player next O = X next B = B next X = O empty :: Grid empty = replicate size (replicate size B) full :: Grid -> Bool full = notElem B . concat turn :: Grid -> Player turn g = if os <= xs then O else X where ps = concat g os = length $ filter (== O) ps xs = length $ filter (== X) ps wins :: Player -> Grid -> Bool wins p g = any line (rows ++ cols ++ dias) where line = all (== p) rows = g cols = transpose g dias = [diag g, diag (map reverse g)] diag :: Grid -> [Player] diag g = [g !! n !! n | n <- [0 .. size - 1]] won :: Grid -> Bool won g = wins O g || wins X g putGrid :: Grid -> IO () putGrid = putStrLn . unlines . concat . interleave bar . map showRow where bar = [replicate ((size * 4) - 1) '-'] showRow :: [Player] -> [String] showRow = beside . interleave bar . map showPlayer where beside = foldr1 $ zipWith (++) bar = replicate 3 "|" showPlayer :: Player -> [String] showPlayer O = [" ", " O ", " "] showPlayer B = [" ", " ", " "] showPlayer X = [" ", " X ", " "] interleave :: a -> [a] -> [a] interleave _ [] = [] interleave x [y] = [y] interleave x (y : ys) = y : x : interleave x ys valid :: Grid -> Int -> Bool valid g i = 0 <= i && i < size ^ 2 && concat g !! i == B move :: Grid -> Int -> Player -> [Grid] move g i p = let (xs, B : ys) = splitAt i $ concat g in ([chop size (xs ++ [p] ++ ys) | valid g i]) chop :: Int -> [a] -> [[a]] chop n [] = [] chop n xs = take n xs : chop n (drop n xs) getNat :: String -> IO Int getNat prompt = do putStr prompt xs <- getLine if xs /= [] && all isDigit xs then return (read xs) else do putStrLn "ERROR: Invalid number" getNat prompt run :: Grid -> Player -> IO () run g p = do cls goto (1, 1) putGrid g run' g p run' :: Grid -> Player -> IO () run' g p | wins O g = putStrLn "Player O wins!" | wins X g = putStrLn "Player X wins!" | full g = putStrLn "It's a draw!" | otherwise = do i <- getNat (prompt p) case move g i p of [] -> do putStrLn "ERROR: Invalid move" run' g p [g'] -> run g' $ next p prompt :: Player -> String prompt p = "Player " ++ show p ++ ", enter your move: " tictactoe :: IO () tictactoe = run empty O depth :: Int depth = 9 data Tree a = Node a [Tree a] deriving (Show) gameTree :: Grid -> Player -> Tree Grid gameTree g p = Node g [gameTree g' (next p) | g' <- moves g p] moves :: Grid -> Player -> [Grid] moves g p | won g = [] | full g = [] | otherwise = concat [move g i p | i <- [0 .. ((size ^ 2) - 1)]] prune :: Int -> Tree a -> Tree a prune 0 (Node x _) = Node x [] prune n (Node x ts) = Node x [prune (n - 1) t | t <- ts] minMax :: Tree Grid -> Tree (Grid, Player) minMax (Node g []) | wins O g = Node (g, O) [] | wins X g = Node (g, X) [] | otherwise = Node (g, B) [] minMax (Node g ts) | turn g == O = Node (g, minimum ps) ts' | turn g == X = Node (g, maximum ps) ts' where ts' = map minMax ts ps = [p | Node (_, p) _ <- ts'] bestMove :: Grid -> Player -> Grid bestMove g p = head [g' | Node (g', p') _ <- ts, p' == best] where tree = prune depth (gameTree g p) Node (_, best) ts = minMax tree play :: Grid -> Player -> IO () play g p = do cls goto (1, 1) putGrid g play' g p play' :: Grid -> Player -> IO () play' g p | wins O g = putStrLn "Player O wins!\n" | wins X g = putStrLn "Player X wins!\n" | full g = putStrLn "It's a draw!\n" | p == O = do i <- getNat (prompt p) case move g i p of [] -> do putStrLn "ERROR: Invalid move" play' g p [g'] -> play g' (next p) | p == X = do putStr "Player X is thinking... " (play $! bestMove g p) (next p) main :: IO () main = do hSetBuffering stdout NoBuffering play empty O
第12章 モナドなど
第13章 モナドパーサー
「まず、パーサーとは何か」の一文めちゃくちゃ石破さんっぽい
- ネットリしてそう
選択肢(alternative)
2つの選択肢から選ぶという操作はアプリカティブな型に対して一般化できる
Alternative
型クラス```haskell class (Applicative f) => Alternative f where empty :: f a (<|>) :: f a -> f a -> f a ```
規則
empty <|> x = x
x <|> empty = x
x <|> (y <|> z) = (x <|> y) <|> z
モナドレキサー
{-# LANGUAGE LambdaCase #-} import Control.Applicative import Data.Bifunctor (first) import Data.Char newtype Parser a = P (String -> [(a, String)]) parse :: Parser a -> String -> [(a, String)] parse (P p) = p item :: Parser Char item = P $ \case [] -> [] (x : xs) -> [(x, xs)] instance Functor Parser where fmap :: (a -> b) -> Parser a -> Parser b fmap f (P p) = P $ \s -> map (first f) (p s) instance Applicative Parser where pure :: a -> Parser a pure a = P $ \s -> [(a, s)] (<*>) :: Parser (a -> b) -> Parser a -> Parser b (<*>) pf px = P $ \s -> case parse pf s of [] -> [] [(f, ss)] -> parse (fmap f px) ss instance Alternative Parser where empty :: Parser a empty = P $ const [] (<|>) :: Parser a -> Parser a -> Parser a (<|>) a b = P $ \s -> case parse a s of [] -> parse b s ar -> ar instance Monad Parser where (>>=) :: Parser a -> (a -> Parser b) -> Parser b (>>=) px mf = P $ \s -> case parse px s of [] -> [] [(a, ss)] -> parse (mf a) ss sat :: (Char -> Bool) -> Parser Char sat p = do c <- item if p c then return c else empty digit :: Parser Char digit = sat isDigit lower :: Parser Char lower = sat isLower upper :: Parser Char upper = sat isUpper letter :: Parser Char letter = sat isAlpha alphanum :: Parser Char alphanum = sat isAlphaNum char :: Char -> Parser Char char c = sat (== c) string :: String -> Parser String string "" = return [] string (x : xs) = do char x string xs return (x : xs) ident :: Parser String ident = do l <- lower s <- many alphanum return (l : s) nat :: Parser Int nat = do i <- some digit return $ read i space :: Parser () space = do many $ sat isSpace return () int :: Parser Int int = do char '-' n <- nat return (-n) <|> nat token :: Parser a -> Parser a token p = do space t <- p space return t identifier :: Parser String identifier = token ident natural :: Parser Int natural = token nat integer :: Parser Int integer = token int symbol :: String -> Parser String symbol xs = token (string xs)
モナドパーサー
{-# LANGUAGE LambdaCase #-} import Control.Applicative import Data.Bifunctor (first) import Data.Char import System.IO (hSetEcho, stdin) newtype Parser a = P (String -> [(a, String)]) parse :: Parser a -> String -> [(a, String)] parse (P p) = p item :: Parser Char item = P $ \case [] -> [] (x : xs) -> [(x, xs)] instance Functor Parser where fmap :: (a -> b) -> Parser a -> Parser b fmap f (P p) = P $ \s -> map (first f) (p s) instance Applicative Parser where pure :: a -> Parser a pure a = P $ \s -> [(a, s)] (<*>) :: Parser (a -> b) -> Parser a -> Parser b (<*>) pf px = P $ \s -> case parse pf s of [] -> [] [(f, ss)] -> parse (fmap f px) ss instance Alternative Parser where empty :: Parser a empty = P $ const [] (<|>) :: Parser a -> Parser a -> Parser a (<|>) a b = P $ \s -> case parse a s of [] -> parse b s ar -> ar instance Monad Parser where (>>=) :: Parser a -> (a -> Parser b) -> Parser b (>>=) px mf = P $ \s -> case parse px s of [] -> [] [(a, ss)] -> parse (mf a) ss sat :: (Char -> Bool) -> Parser Char sat p = do c <- item if p c then return c else empty digit :: Parser Char digit = sat isDigit lower :: Parser Char lower = sat isLower upper :: Parser Char upper = sat isUpper letter :: Parser Char letter = sat isAlpha alphanum :: Parser Char alphanum = sat isAlphaNum char :: Char -> Parser Char char c = sat (== c) string :: String -> Parser String string "" = return [] string (x : xs) = do char x string xs return (x : xs) ident :: Parser String ident = do l <- lower s <- many alphanum return (l : s) nat :: Parser Int nat = do i <- some digit return $ read i space :: Parser () space = do many $ sat isSpace return () comment :: Parser () comment = do string "--" many $ sat (/= '\n') return () int :: Parser Int int = do char '-' n <- nat return (-n) <|> nat token :: Parser a -> Parser a token p = do space many comment t <- p space return t identifier :: Parser String identifier = token ident natural :: Parser Int natural = token nat integer :: Parser Int integer = token int symbol :: String -> Parser String symbol xs = token (string xs) -- 文法規則 expr :: Parser Int expr = do t <- term do symbol "+" e <- expr return (t + e) <|> do symbol "-" e <- expr return (t - e) <|> return t term :: Parser Int term = do e <- expo do symbol "*" t <- term return (e * t) <|> do symbol "/" t <- term return (e `div` t) <|> return e expo :: Parser Int expo = do f <- factor do symbol "^" e <- expo return (f ^ e) <|> return f factor :: Parser Int factor = do symbol "(" e <- expr symbol ")" return e <|> integer -- 表示 cls :: IO () cls = putStr "\ESC[2J" beep :: IO () beep = putStr "\BEL" goto :: (Int, Int) -> IO () goto (x, y) = putStr $ "\ESC[" ++ show y ++ ";" ++ show x ++ "H" writeAt :: (Int, Int) -> String -> IO () writeAt p s = do goto p putStr s getCh :: IO Char getCh = do hSetEcho stdin False x <- getChar hSetEcho stdin True return x box :: [String] box = [ "+---------------+", "| |", "+---+---+---+---+", "| q | c | d | = |", "+---+---+---+---+", "| 1 | 2 | 3 | + |", "+---+---+---+---+", "| 4 | 5 | 6 | - |", "+---+---+---+---+", "| 7 | 8 | 9 | * |", "+---+---+---+---+", "| 0 | ( | ) | / |", "+---+---+---+---+" ] buttons :: String buttons = standard ++ extra where standard = "qcd=123+456-789*0()/^" extra = "QCD \ESC\BS\DEL\n" showBox :: IO () showBox = sequence_ [writeAt (1, y) b | (y, b) <- zip [1 ..] box] display :: String -> IO () display xs = do writeAt (3, 2) (replicate 13 ' ') writeAt (3, 2) (reverse (take 13 (reverse xs))) calc :: String -> IO () calc xs = do display xs c <- getCh if c `elem` buttons then process c xs else do beep calc xs process :: Char -> String -> IO () process c xs | c `elem` "qQ\ESC" = quit | c `elem` "dD\BS\DEL" = delete xs | c `elem` "=\n" = eval xs | c `elem` "cC" = clear | otherwise = press c xs quit :: IO () quit = goto (1, 14) delete :: String -> IO () delete "" = calc [] delete xs = calc (init xs) eval :: String -> IO () eval xs = case parse expr xs of [(n, [])] -> calc (show n) [(_, ss)] -> calc ("ERROR: " ++ ss) _ -> do beep calc xs clear :: IO () clear = calc [] press :: Char -> String -> IO () press c xs = calc (xs ++ [c]) run :: IO () run = do cls showBox clear
第14章 FoldableとTraversable
Foldable
データ構造をモノイドを用いて畳み込むことが可能であることを示す
```haskell class Foldable t where fold :: (Monoid a) => t a -> a -- デフォルト実装有 foldMap :: (Monoid b) => (a -> b) -> t a -> b foldr :: (a -> b -> b) -> b -> t a -> b foldl :: (a -> b -> a) -> a -> t b -> a -- デフォルト実装有 instance Foldable MyMaybe where fold :: (Monoid m) => MyMaybe m -> m fold MyNothing = mempty fold (MyJust a) = a foldMap :: (Monoid m) => (a -> m) -> MyMaybe a -> m foldMap _ MyNothing = mempty foldMap f (MyJust a) = f a foldr :: (a -> b -> b) -> b -> MyMaybe a -> b foldr _ b MyNothing = b foldr f b (MyJust a) = f a b foldl :: (b -> a -> b) -> b -> MyMaybe a -> b foldl _ b MyNothing = b foldl f b (MyJust a) = f b a ```
- 最低限
foldMap
もしくはfoldr
を実装すれば良い foldr
、foldl
にモノイドの型制約ないんやね
- 最低限
toList
- そりゃ
append
していったらリストにできるわな
- そりゃ
Traversable
各要素に関数を適用していくような状況の中で、その関数が文脈を持った値を返すようなときでも適用できるように一般化したやつ
```haskell class (Functor t, Foldable t) => Traversable t where traverse :: (Applicative f) -> (a -> f b) -> t a -> f (t b) instance Traversable MyMaybe where traverse :: (Applicative f) => (a -> f b) -> MyMaybe a -> f (MyMaybe b) traverse f MyNothing = pure MyNothing traverse f (MyJust a) = MyJust <$> f a ```
Functor
があって、Foldable
な値にApplicative
な値を返す関数であれば適用できるように抽象化したと捉えられる
例えば
sequenceA
```haskell sequenceA :: (Applicative f) -> (a -> f b) -> t (f a) -> f (t b) sequenceA = traverse id ```
「失敗するかもしれない値を要素に持つリスト」を、「全体が失敗するかもしれない値」に変換できる
```haskell sequenceA [Just 1, Just 2, Just 3] -> Just [1, 2, 3] ```
第15章 遅延評価
- 簡約可能式(リデックス)
- 1つ以上の引数に対して適用されている関数があって、それを適用することで簡約できる式
- 簡約したとしても式の大きさが小さくなるとは限らない
- 最内簡約
- 最も内側の簡約可能式を選択する手法
- 他の簡約可能式を含まないという意味
- もし複数あるなら左側から簡約する
- 引数が関数に渡される前に評価済であることが保証されている
- 常に値として渡される
- 最も内側の簡約可能式を選択する手法
- 最外簡約
- 最も外側の簡約可能式を選択する手法
- 他の簡約可能式に含まれていないという意味
- もし複数あるなら左側から簡約する
- 引数が評価前に渡されている
- 名前で渡されていると言える
- 最も外側の簡約可能式を選択する手法
- Haskellでは関数内(ラムダ式含む)の簡約が禁止されている
- 中を覗けないから
- 関数を引数に適用することしか許されていない
- 中は完全の評価済みであるとしている
- ある式に対し停止する評価の手法が存在するなら、名前渡しはその式の評価を必ず停止させ、同じ結果を返す
- 評価をなるべく多く停止させたいなら、値渡しより名前渡しの方が良い
- なお簡約の回数は増える模様
- 引数をポインタで共有すれば解決できるぞ
- 遅延評価という
- 引数をポインタで共有すれば解決できるぞ
- なお簡約の回数は増える模様
- 評価をなるべく多く停止させたいなら、値渡しより名前渡しの方が良い
- なぜ遅延評価(名前渡し)では無限のデータ構造が扱えるか
以下の関数について考える
```haskell -- ones = 1 : ones head ones -- head (x : _) -> x ```
値渡しだと引数の評価を先に行う
```haskell head (1 : ones head (1 : (1 : ones ... ```
- 停止しない
名前渡しは関数の適用を先に行う
```haskell head (1 : ones) -> 1 ```
- 停止する
- 引数は必要な分だけ評価される
$!
- 正格評価演算子
第16章
Haskellの等式推論
- Haskellでは組み込み関数のほか、ユーザー定義関数も等式推論に利用している
double x = x + x
において、double x
をx + x
に置換しても良いし、x + x
をdouble x
に置換しても良い- だから関数定義時は
=
で結んでるのか
- だから関数定義時は
- 複数の等式を用いて定義された場合の関数は、定義順序に意味があるので、置き換えできるかどうか判定するのに他の定義の確認が必要
重複のない定義をすることによって対策可能
```haskell isZero 0 = True isZero n | n /= 0 = False ```
- 重複なし!
第17章
- なるほど