Haskell
概要
Haskellは純粋関数型Programming Language。
Memo
QuickCheck
QuickCheckというテストライブラリがある。型に基づくプロパティテスト、というもののよう。
stack
stackはHaskellの開発ツール。
REPL
対話実行環境を立ち上げる。
ghci >> :load -- ファイル読み込み >> :reload -- 再読込。org-babelがおかしくなったときに使える >> :quit -- ghci終了
プロンプトをセット。
:set prompt "GHCi> "
前置引数
(+) 2 2
4
マイナス演算子
2 + (- 1)
1
:infoコマンド
infixl
が演算子の優先順位。
:info (+)
class Num a where (+) :: a -> a -> a … – Defined in ‘GHC.Num’ infixl 6 +
:info (*)
class Num a where … (*) :: a -> a -> a … – Defined in ‘GHC.Num’ infixl 7 *
定数
pi
3.141592653589793
代入
let e = exp 1 (e ** pi) - pi
19.99909997918947
リスト
["foo", "bar", "baz"]
[“foo”,“bar”,“baz”]
列挙表記
[1..10]
[1,2,3,4,5,6,7,8,9,10]
[1.0,1.25..2.0]
[1.0,1.25,1.5,1.75,2.0]
リスト結合
[3,1,3] ++ [3,7]
[3,1,3,3,7]
cons
1 : [2, 3]
[1,2,3]
型情報
⚠babelで実行すると実行されなくなるので、GHCIだけで試すこと。
Prelude> :set +t Prelude> 'c' 'c' it :: Char Prelude> :unset +t
型エラー
1.2 % 3.4
<interactive>:6213:5: error: Variable not in scope: (%) :: Double -> Double -> t
:type
:type 1
1 :: Num p => p
:type "abc"
“abc” :: [Char]
:type it
it :: [Char]
いくつかの数値型関数
succ 6
7
pred 9
8
sin (pi / 2)
1.0
truncate pi
3
round 3.4
3
:?
コマンド
:?
display this list of commands :info[!] [<name> …] display information about the given names (!: do not filter instances) :issafe [<mod>] display safe haskell information of module <mod> :kind[!] <type> show the kind of <type> (!: also print the normalised type) :load[!] [*]<module> … load module(s) and their dependents (!: defer type errors) :main [<arguments> …] run the main function with the given arguments :module [+/-] [*]<mod> … set the context for expression evaluation :quit exit GHCi :reload[!] reload the current module set (!: defer type errors) :run function [<arguments> …] run the function with the given arguments :script <file> run the script <file> :type <expr> show the type of <expr> :type +d <expr> show the type of <expr>, defaulting type variables :type +v <expr> show the type of <expr>, with its specified tyvars :unadd <module> … remove module(s) from the current target set :undef <cmd> undefine user-defined command :<cmd> :!<command> run the shell command <command>
– Commands for debugging:
:abandon at a breakpoint, abandon current computation :back [<n>] go back in the history N steps (after :trace) :break [<mod>] <l> [<col>] set a breakpoint at the specified location :break <name> set a breakpoint on the specified function :continue resume after a breakpoint :delete <number> delete the specified breakpoint :delete * delete all breakpoints :force <expr> print <expr>, forcing unevaluated parts :forward [<n>] go forward in the history N step s(after :back) :history [<n>] after :trace, show the execution history :list show the source code around current breakpoint :list <identifier> show the source code for <identifier> :list [<module>] <line> show the source code around line number <line> :print [<name> …] show a value without forcing its computation :sprint [<name> …] simplified version of :print :step single-step after stopping at a breakpoint :step <expr> single-step into <expr> :steplocal single-step within the current top-level binding :stepmodule single-step restricted to the current module :trace trace after stopping at a breakpoint :trace <expr> evaluate <expr> with tracing on (see :history)
– Commands for changing settings:
:set <option> … set options :seti <option> … set options for interactive evaluation only :set args <arg> … set the arguments returned by System.getArgs :set prog <progname> set the value returned by System.getProgName :set prompt <prompt> set the prompt used in GHCi :set prompt-cont <prompt> set the continuation prompt used in GHCi :set prompt-function <expr> set the function to handle the prompt :set prompt-cont-function <expr>set the function to handle the continuation prompt :set editor <cmd> set the command used for :edit :set stop [<n>] <cmd> set the command to run when a breakpoint is hit :unset <option> … unset options
Options for ’:set’ and ’:unset’:
+m allow multiline commands +r revert top-level expressions after each evaluation +s print timing/memory stats after each evaluation +t print type after evaluation +c collect type/location info after loading modules -<flags> most GHC command line flags can also be set here (eg. -v2, -XFlexibleInstances, etc.) for GHCi-specific flags, see User’s Guide, Flag reference, Interactive-mode options
– Commands for displaying information:
:show bindings show the current bindings made at the prompt :show breaks show the active breakpoints :show context show the breakpoint context :show imports show the current imports :show linker show current linker state :show modules show the currently loaded modules :show packages show the currently active package flags :show paths show the currently active search paths :show language show the currently active language flags :show targets show the current set of targets :show <setting> show value of <setting>, which is one of [args, prog, editor, stop] :showi language show language flags for interactive evaluation
型推論
式の後ろに書く::と型の組み合わせのことを 型シグネチャ
という。
:type 'a' 'a' :: Char
’a’ :: Char ’a’
[1, 2, 3] :: Int
In an equation for ‘it’: it = [1, 2, 3] :: Int
関数適用
odd 3
True
odd 6
False
compare 3 3
EQ
compare 3 2
GT
(compare 2 3) == LT compare 2 3 == LT
True True
compare (sqrt 3) (sqrt 6)
LT
合成データ型: リスト
合成データ型は他のデータ型から構成する。よく使う合成データ型はリスト(可変長)とタプル(固定長)。
リスト型は 多相的
である。多相型を書く場合には 型変数
を使う。
head :: [a] -> a
は「どんな型の値を含むかはわからないが、その型の値のリストは作れる」と言っている。
:type head head [1,2,3,4] head ['a','b','c'] tail [1,2,3,4] tail [True,False]
<interactive>:5924:1-17: error: • No instance for (Show Bool) arising from a use of ‘print’ There are instances for similar types: instance Show Prelude.Bool – Defined in ‘GHC.Show’ • In a stmt of an interactive GHCi command: print it
-- 評価結果のBoolがリンクで解釈されてエクスポート時にエラーになるので再評価しない。 :type [[True],[False,False]]
[[True],[False,False]] :: [[Bool]]
異なる型ではエラー。
:type [1, 'a']
<interactive>:1:2: error: • No instance for (Num Char) arising from the literal ‘1’ • In the expression: 1 In the expression: [1, ’a’]
合成データ型: タプル
リストと違ってタプルは型が異なっていてもOK。
(1964, "Labyrinths") ()
(1964,“Labyrinths”) ()
:type (True, "hello")
(True, “hello”) :: (Bool, [Char])
:type (4, ['a', 'm'], (16, True))
(4, [’a’, ’m’], (16, True)) :: (Num a1, Num a2) => (a1, [Char], (a2, Bool))
タプルの型は数/位置/その位置に含まれる要素の型、で表す。 含まれている要素の型や数が異なれば別の型。
:type (False, 'a')
(False, ’a’) :: (Bool, Char)
:type ('a', False)
(’a’, False) :: (Char, Bool)
:type (False, 'a')
(False, ’a’) :: (Bool, Char)
関数
take 2 [1,2,3,4,5]
[1,2]
drop 3 [1,2,3,4,5]
[4,5]
⚠ fst(1,'a')
と空白なしで書くと、引数2つに見えるが、違うことに注意。
単一のペアへの関数の適用である。
fst (1,'a')
1
snd (1,'a')
’a’
関数の型
:type lines
lines :: String -> [String]
lines "the quick\nbrown for\njumps"
[“the quick”,“brown for”,“jumps”]
副作用がある関数で結果の型は IO
ではじまる。
:type readFile
readFile :: FilePath -> IO String
関数定義
Haskellのコードの中では、記号 =
は「〜を意味する」ということ。左辺の名前は右辺の式であると定義される。
変数は式に名前をつける手段。
命令形言語を使ってきたのなら、変数は、異なる辞典で、異なる値を格納し得るメモリの番地(またはそれに類するもの)を特定する方法と考えるのが自然かもしれません。命令形言語では、いつでも変数の値を変更することが可能です。したがって、その変数のメモリ番地を確認するたびに違う結果になりえます。 変数に対する、この2つの概念には決定的な違いがあります。Haskellでは、式にいったん束縛した変数は、いつでも元の式に置き換えても良いのです。変数の値は変わらないからです。命令形言語はこの置き換え可能性を保持していません。
add a b = a + b add 1 2
3
drop 2 "foobar" drop 4 "foobar" drop 4 [1, 2] drop 0 [1, 2] drop 7 [] drop (-2) "foo"
obar ar [] [1,2] [] foo
myDrop関数
インデントによって既存の定義が継続する。
org-babelでは1行ごとで新しいセッションになってしまうよう。 :{
と }:
を使って複数行評価されるようにする。
変数名 xs
は x
の複数形という意味。
式指向のためelseが必須(elseに入ったとき結果や型がないという事態になるため)。
:{ myDrop n xs = if n <= 0 || null xs then xs else myDrop (n - 1) (tail xs) :} myDrop 2 "foobar" myDrop 4 "foobar" myDrop 4 [1, 2] myDrop 0 [1, 2] myDrop 7 [] myDrop(-2) "foo"
GHCi> “obar” ar [] [1,2] [] foo
論理演算子。
:type null :type (||)
null :: Foldable t => t a -> Prelude.Bool (||) :: Prelude.Bool -> Prelude.Bool -> Prelude.Bool
遅延評価
正格評価: 関数を適用する前に関数への引数を評価する。 非正格評価(遅延評価): 評価せず、値が必要になったとき計算する「プロミス」を作る。未評価の式を追跡するのに使うレコードを サンク という。式の結果が使われないなら、値は計算されない。
isOdd n = mod n 2 == 1 isOdd 3
True
print (myDrop 2 "abcd") :type 2 <= 0 || null "abcd" null "abcd"
cd GHCi> 2 <= 0 || null “abcd” :: Prelude.Bool False
型変数
last [1,2,3] last "baz" :type last
3 ’z’ last :: [a] -> a
この a
が型変数。
型シグネチャに型変数を持つときには引数のいくつかはどのような型にでもなれることを示している。→ 多相的である。
多相関数
:type fst
fst :: (a, b) -> a
:type take
take :: Int -> [a] -> [a]
これは実際にはこういうこと↓。
take :: Int -> ([a] -> [a])
純粋性
:type not
not :: Prelude.Bool -> Prelude.Bool
型定義
↓ BookInfoを 型構成子
という。
続く Bookは 値構成子
という。
Int, String, [String]は 構成要素
という。
:{ data BookInfo = Book Int String [String] deriving (Show) :}
同じ構造を持っていても、2つの型を区別する。型構成子と値構成子の名前が違うから。
:{ data MagazineInfo = Magazine Int String [String] deriving (Show) :}
:info BookInfo myInfo = Book 9780135072455 "Algebra of Programming" ["Richard Bird", "Oege de Moor"] myInfo :type myInfo :type Book
data BookInfo = Book Int String [String] – Defined at <interactive>:6313:1 instance [safe] Show BookInfo – Defined at <interactive>:6314:27 GHCi> GHCi> Book 9780135072455 “Algebra of Programming” [“Richard Bird”,“Oege de Moor”] myInfo :: BookInfo Book :: Int -> String -> [String] -> BookInfo
型シノニム
型シノニムはコードを読みやすくするだけのもの。
type CustomerID = Int type ReviewBody = String data BetterReview = BetterReview BookInfo CustomerID ReviewBody
代数データ型
値構成子が2つある。 選択肢 あるいは ケース という。
data Bool = False | True
type CardHolder = String type CardNumber = String type Address = [String] :{ data BillingInfo = CreditCard CardNumber CardHolder Address | CashOnDelivery | Invoice CustomerID deriving (Show) :} :info BillingInfo
GHCi> GHCi> GHCi> Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| GHCi> data BillingInfo = CreditCard CardNumber CardHolder Address
CashOnDelivery |
Invoice CustomerID |
– Defined at <interactive>:6340:1 instance [safe] Show BillingInfo – Defined at <interactive>:6343:32
:type CreditCard CreditCard "2901650221064486" "Thomas Gradgrind" ["Dickens", "England"]
CreditCard :: CardNumber -> CardHolder -> Address -> BillingInfo CreditCard “2901650221064486” “Thomas Gradgrind” [“Dickens”,“England”]
:type it
it :: [Char]
:type Invoice Invoice 2222
Invoice :: CustomerID -> BillingInfo Invoice 2222
タプルと代数データ型
Book 2 "The Wealth of Networks" ["Yochai Benkler"] (2, "The Wealth of Networks", ["Yochai Benkler"])
Book 2 “The Wealth of Networks” [“Yochai Benkler”] (2,“The Wealth of Networks”,[“Yochai Benkler”])
↓構造的に同一なので同じ型。
a = ("Porpoise", "Gray") b = ("Table", "Oak")
↓別の名前なので別の型。
data Cetacean = Cetacean String String data Furniture = Furniture String String c = Cetacean "Porpoise" "Gray" d = Furniture "Table" "Oak"
座標。
:{ -- x, yの座標 data Cartesian2D = Cartesian2D Double Double deriving (Eq, Show) -- 偏角と長さ data Polar2D = Polar2D Double Double deriving (Eq, Show) :}
==
演算子は引数が同じ型でないといけないのでエラー。
Cartesian2D (sqrt 2) (sqrt 2) == Polar2D (pi / 4) 2
In an equation for ‘it’: it = Cartesian2D (sqrt 2) (sqrt 2) == Polar2D (pi / 4) 2
タプルの場合判断しようがないのでチェックが効かない。
(1, 2) == (1, 2)
True
- 複合型をコードの中で広く使う場合には
data
宣言を使う - 規模が小さく、局所的にしか使わない場合はタプルでいい
他の言語との類似
struct book_info { int id; char *name; char **authors; };
:{ data BookInfo = Book Int String [String] deriving (Show) :}
列挙型
C言語ではこう書く。
enum roygbiv { red, orange, yellow, green, blue, indigo, violet, };
:{ data Roygbiv = Red | Orange | Yellow | Green | Blue | Indigo | Violet deriving (Eq, Show) :} :type Yellow :type Red Red == Yellow Green == Green
Yellow :: Roygbiv Red :: Roygbiv False True
haskellのenumは型が厳密。
take 3 "foobar" take Red "foobar"
foo <interactive>:6405:6-8: error: • Couldn’t match expected type ‘Int’ with actual type ‘Roygbiv’ • In the first argument of ‘take’, namely ‘Red’ In the expression: take Red “foobar” In an equation for ‘it’: it = take Red “foobar”
union
代数データ型が複数の選択肢を持つ場合はC言語の union
と同じ。
type Vector = (Double, Double) :{ data Shape = Circle Vector Double | Poly [Vector] :} :info Shape
GHCi> data Shape = Circle Vector Double | Poly [Vector] – Defined at <interactive>:6409:1
パターンマッチ
↓2つの関数というわけではない。 同一関数の異なる入力パターンに対する振る舞いを定義している。
myNot True = False myNot False = True
sumList (x:xs) = x + sumList xs sumList [] = 0 sum [1,2]
GHCi> 3
タプルのパターンマッチ。
complicated (True, a, x:xs, 5) = (a, xs) complicated (True, 1, [1,2,3], 5)
(1,[2,3])
ぴったり一致しないと成功しない。 すべてのパターンマッチが失敗すると実行時エラー。
complicated (False, 1, [1,2,3], 5)
*** Exception: <interactive>:1963:1-40: Non-exhaustive patterns in function complicated
Exception: <interactive>:6422:1-40: Non-exhaustive patterns in function complicated
BookInfo型の場合。 パターンマッチをセット。
bookID (Book id title authors) = id bookTitle (Book id title authors) = title bookAuthors (Book id title authors) = authors
アクセスできる。
bookID (Book 3 "Probability Theory" ["E.T.H. Jaynes"]) bookTitle (Book 3 "Probability Theory" ["E.T.H. Jaynes"]) bookAuthors (Book 3 "Probability Theory" ["E.T.H. Jaynes"])
3 Probability Theory [“E.T.H. Jaynes”]
構成子に基づいて、アクセサ関数の型を推論できる。
:type bookID :type bookTitle :type bookAuthors
bookID :: BookInfo -> Int bookTitle :: BookInfo -> String bookAuthors :: BookInfo -> [String]
ワイルドカード
この類のコードをボイラープレートという。
nicerID (Book id _ _ ) = id nicerTitle (Book _ title _) = title nicerAuthors (Book _ _ authors) = authors
goodExample (x:xs) = x + goodExample xs goodExample _ = 0 goodExample [] goodExample [1, 2]
GHCi> 0 0
レコード構文
:{ data Customer = Customer { customerID :: CustomerID , customerName :: String , customerAddress :: Address } deriving (Show) :} :type customerID
customerID :: Customer -> CustomerID
:{ customer2 = Customer { customerID = 271828 , customerAddress = ["1048576 Disk Drive", "Milpitas, CA 95134", "USA"] , customerName = "Jane Q. Citizen" } :} customer2 cities
Customer {customerID = 271828, customerName = “Jane Q. Citizen”, customerAddress = [“1048576 Disk Drive”,“Milpitas, CA 95134”,“USA”]} Book 173 “Use of weapons” [“Iain M. Banks”]
レコード構文によって使えるアクセサ関数は、通常のHaskellの関数。
:type customerName customerName customer2
customerName :: Customer -> String Jane Q. Citizen
パラメータ化された型
独自に定義する型も多相型にできる。型宣言に型変数を導入する。
↓この変数は型変数。
:{ data Maybe a = Just a | Nothing :} :info Maybe
data Maybe a = Just a | Nothing – Defined at <interactive>:6475:1
これによって任意の型上の Maybe
型を使える。
someBool = Just True :type someBool someString = Just "something" :type someString :type Just "Invisible bike"
someBool :: Maybe Bool GHCi> someString :: Maybe [Char] Just “Invisible bike” :: Maybe [Char]
wrapped = Just (Just "wrapped") :type wrapped
wrapped :: Maybe (Maybe [Char])
再帰型
リスト型は再帰型。定義に自身が含まれる。
:{ data List a = Cons a (List a) | Nil deriving (Show) :}
確かめる。
Nil Cons 0 Nil Cons 1 it Cons 2 it Cons 3 it
Nil Cons 0 Nil Cons 1 (Cons 0 Nil) Cons 2 (Cons 1 (Cons 0 Nil)) Cons 3 (Cons 2 (Cons 1 (Cons 0 Nil)))
二分木。
:{ data Tree a = Node a (Tree a) (Tree a) | Empty deriving (Show) :}
本と違ってなぜかエラーになる。
fromList (x:xs) = Cons x (fromList xs) fromList [] = Nil fromList "durian" fromList [Just True, Nothing, Just False]
GHCi> * Exception: <interactive>:6518:1-17: Non-exhaustive patterns in function fromList Exception: <interactive>:6518:1-17: Non-exhaustive patterns in function fromList
Javaの例(クラス定義)。
class Tree<A> { A value; Tree<A> left; Tree<B> right; public Tree(A v, Tree<A> l, Tree<A> r) { value = v; left = l; right = r; } }
葉を構成する関数。
class Example { static Tree<String> simpleTree() { return new Tree<String>( "parent", new Tree<String>("left leaf", null, null), new Tree<String>("right leaf", null, null)); } }
:{ simpleTree = Node "parent" (Node "left child" Empty Empty) (Node "right child" Empty Empty) :} :type simpleTree
simpleTree :: Tree [Char]
エラー報告表示
listの要素が1つのときはエラー表示を出す。
:{ mySecond :: [a] -> a mySecond xs = if null (tail xs) then error ("list too short") else head (tail xs) :} mySecond "xi" mySecond [2, 3] mySecond [2]
’i’ 3 Exception: list too short CallStack (from HasCallStack): error, called at <interactive>:6531:20 in interactive:Ghci3189
ちゃんとエラーメッセージが出ている。
エラーの可能性, Maybe
:{ safeSecond :: [a] -> Maybe a safeSecond [] = Nothing safeSecond xs = if null (tail xs) then Nothing else Just (head (tail xs)) :} safeSecond [1,2] -- なぜかできない。
<interactive>:6545:1-16: error: • No instance for (Show (Maybe Integer)) arising from a use of ‘print’ There are instances for similar types: instance Show a => Show (Prelude.Maybe a) – Defined in ‘GHC.Show’ • In a stmt of an interactive GHCi command: print it
パターンマッチを使って改善。
tidySecond :: [a] -> Maybe a tidySecond (_:x:_) = Just x tidySecond _ = Nothing
局所変数
:{ lend amount balance = let reserve = 100 newBalance = balance - amount in if balance < reserve then Nothing else Just newBalance :}
シャドウ
内側の x
が外側の x
を隠す。
:{ bar = let x = 1 in ((let x = "foo" in x), x) :} bar
(“foo”,1)
where節
:{ lend2 amount balance = if amount < reserve * 0.5 then Just newBalance else Nothing where reserve = 100 newBalance = balance - amount :}
:{ pluralise :: String -> [Int] -> [String] pluralise word counts = map plural counts where plural 0 = "no " ++ word ++ "s" plural 1 = "one " ++ word plural n = show n ++ " " ++ word ++ "s" :} pluralise "car" [0] pluralise "car" [1] pluralise "car" [2]
[“no cars”] [“one car”] [“2 cars”]
case式
:{ fromMaybe defval wrapped = case wrapped of Nothing -> defval Just value -> value :}
:{ data Fruit = Apple | Orange betterFruit f = case f of "apple" -> Apple "orange" -> Orange :}
ガード
:{ nodesAreSame (Node a _ _) (Node b _ _) | a == b = Just a nodesAreSame _ _ = Nothing :}
lend関数を書き直す。
:{ lend3 amount balance | amount <= 0 = Nothing | amount > reserve * 0.5 = Nothing | otherwise = Just newBalance where reserve = 100 newBalance = balance - amount :}
myDrop関数を書き直す。 元コード。
:{ myDrop n xs = if n <= 0 || null xs then xs else myDrop (n - 1) (tail xs) :} myDrop 2 "abcd"
cd
:{ niceDrop n xs | n <= 0 = xs niceDrop _ [] = [] niceDrop n (_:xs) = niceDrop (n - 1) xs :} niceDrop 2 "abcd"
cd
テキスト行分割
:type lines lines "line 1\nline 2" lines "foo\n\nbar\n"
lines :: String -> [String] [“line 1”,“line 2”] [“foo”,“”,“bar”]
break odd [2,4,5,6,8] :module +Data.Char break isUpper "isUpper"
([2,4],[5,6,8]) GHCi> (“is”,“Upper”)
:{ a `plus` b = a + b data a `Pair` b = a `Pair` b deriving (Show) foo = Pair 1 2 bar = True `Pair` "quux" :} 1 `plus` 2 plus 1 2 Pair "a" "a" "a" `Pair` "a"
3 3 a“ `Pair` ”a a“ `Pair` ”a
リストの処理
:type length length [] length [1,2,3] null [] head [1,2] tail "foo" last "bar"
length :: Foldable t => t a -> Int 0 3 True 1 oo ’r’
連結関数。
:type (++) "foo" ++ "bar" [] ++ [1,2,3] [1] ++ []
(++) :: [a] -> [a] -> [a] foobar [1,2,3] [1]
concat
連結して1つのリストにする。
:type concat concat [[1,2,3], [4,5,6]] concat [[[1,2],[3]], [[4],[5],[6]]] concat (concat [[1,2],[3]], [[4],[5],[6]])
concat :: Foldable t => t [a] -> [a] [1,2,3,4,5,6] [[1,2],[3],[4],[5],[6]] [[4],[5],[6]]
reverse
逆順にする。
:type reverse reverse "foo"
reverse :: [a] -> [a] oof
便利な条件判定、 all
と any
。
:type all all odd [1,3,5] all odd [3,1,4] all odd [] :type any any even [3,1,4] any even []
all :: Foldable t => (a -> Prelude.Bool) -> t a -> Prelude.Bool True False True any :: Foldable t => (a -> Prelude.Bool) -> t a -> Prelude.Bool True False
部分リスト。
:type take take 3 "foobar" take 2 [1] :type drop drop 3 "xyzzy" drop 1 []
take :: Int -> [a] -> [a] foo [1] drop :: Int -> [a] -> [a] zy []
splitAt
インデックスで分割したリストのペアを返す。
:type splitAt splitAt 3 "foobar"
splitAt :: Int -> [a] -> ([a], [a]) (“foo”,“bar”)
span
条件に合うもの、以外でリストを返す。
:type span span even [2,4,6,7,9,10,11] :type break break even [1,3,5,6,8,9,10]
span :: (a -> Prelude.Bool) -> [a] -> ([a], [a]) ([2,4,6],[7,9,10,11]) break :: (a -> Prelude.Bool) -> [a] -> ([a], [a]) ([1,3,5],[6,8,9,10])
elem
値がリスト中に存在するか示す。
:type elem 2 `elem` [5,3,2,1,1] 2 `notElem` [5,3,2,1,1]
elem :: (Foldable t, Eq a) => a -> t a -> Prelude.Bool True False
filter
条件に合うリストを返す。
:type filter filter odd [2,4,1,3,6,8,5,7]
filter :: p -> [a1] -> [a2] Exception: <interactive>:4825:1-16: Non-exhaustive patterns in function filter
isPrefixOf
部分リストがより大きなリストの中にあるか調べる。
:module +Data.List :type isPrefixOf "foo" `isPrefixOf` "foobar" [1,2] `isPrefixOf` []
isPrefixOf :: Eq a => [a] -> [a] -> Prelude.Bool True False
zip
2つのリストをペアのリストにする。
:type zip zip [12,72,93] "zippity"
zip :: [a] -> [b] -> [(a, b)] [(12,’z’),(72,’i’),(93,’p’)]
zipWith
2つのリストのペアごとに関数を適用する。
:type zipWith zipWith (+) [1,2,3] [4,5,6]
zipWith :: (a -> b -> c) -> [a] -> [b] -> [c] [5,7,9]
可変長引数はHaskellの型システムのうえでは難しい。
複数のリストを閉じ合わせるには zip3
〜 zip7`
を使う。
リストのいけてる扱い方
haskellにおいてはほかの言語と同じように、扱えない部分がある。
length
はリスト全体を辿らないといけないが、無限リストである可能性がある。
:{ mySmartExample xs = if not (null xs) then head xs else 'Z' myOtherExample (x:_) = x myOtherExample [] = 'Z' :} mySmartExample "head" mySmartExample []
’h’ ’Z’
部分関数
正当な入力の部分集合の値に対してのみ値を返す関数のことを部分関数という。 入力の全定義域に対して正しい結果を返す関数のことを全関数という。
文字列専用の関数
lines "foo\nbar" unlines ["foo", "bar"]
[“foo”,“bar”] foo\nbar\n
words
は入力文字を任意の空白で区切る。
words "the \r quick \t brown\n\n\nfox" unwords ["jumps", "over", "the", "lazy", "dog"]
[“the”,“quick”,“brown”,“fox”] jumps over the lazy dog
ループ
Haskellには、 for
ループ、 while
ループはない。
C言語の例。 こんなふうにはできないので末尾再帰を使う。
int as_int(char *str) { int acc; for (acc = 0; isdigit(*str); str++) { acc = acc * 10 + (*str - '0'); } return acc; }
配列の中のすべての要素を2乗する。
void square(double *out, const double *in, size_t length) { for (size_t i = 0; i < length; i++) { out[i] = in[i] * in[i]; } }
:{ square :: [Double] -> [Double] square (x:xs) = x*x : square xs square [] = [] :} square [1,2] square []
[1.0,4.0] []
大文字化。
:{ upperCase :: String -> String upperCase (x:xs) = toUpper x : upperCase xs upperCase [] = [] :} upperCase "hello"
HELLO
map
は関数を引数としてとり、リストのすべての要素に適用する。
upperCase2 xs = map toUpper xs upperCase2 "hello"
HELLO
map
を書き直してみる。
:{ myMap :: (a -> b) -> [a] -> [b] myMap f (x:xs) = f x : myMap f xs myMap _ _ = [] :} upperCase3 xs = myMap toUpper xs upperCase3 "hello"
GHCi> “HELLO”
フィルター
:{ oddList :: [Int] -> [Int] oddList (x:xs) | odd x = x : oddList xs | otherwise = oddList xs oddList _ = [] :} oddList [1,2,3,4,5,6,7,8,9,10]
[1,3,5,7,9]
helper
は末尾再帰の関数で、蓄積変数 acc
を使ってリストのその時点での部分和を保持する。
:{ mySum xs = helper 0 xs where helper acc (x:xs) = helper (acc + x) xs helper acc _ = acc :} mySum [1,2,3,4]
10
畳み込み
畳み込み: リストのすべての要素に何かを行い、蓄積変数を更新していって、終わったところで蓄積変数を返す。
:{ foldl :: (a -> b -> a) -> a -> [b] -> a foldl step zero (x:xs) = foldl step (step zero x) xs foldl _ zero [] = zero :}
:{ niceSum :: [Integer] -> Integer niceSum xs = foldl (+) 0 xs :} niceSum [1,2,3,4,5]
15
問題を2つに単純化することで、すっきり書けるようになった。 蓄積値の初期値をどうするかと、蓄積変数の更新。
明示的再帰は綿密に読む必要があり、わかりづらい。 畳み込みを使えば再帰を理解しやすい。
右側からの畳み込み。
foldr :: (a -> b -> b) -> b -> [a] -> b foldr step zero (x:xs) = step x (foldr step zero xs) foldr _ zero [] = 0
再帰と畳み込みの比較
:{ filter :: (a -> Bool) -> [a] -> [a] filter p [] = [] filter p (x:xs) | p x = x : filter p xs | otherwise = filter p xs :}
:{ myFilter p xs = foldr step [] xs where step x ys | p x = x : ys | otherwise = ys :} myFilter (+) [1,2]
無名関数
普通に書く例。
:{ isInAny needle haystack = any inSequence haystack where inSequence s = needle `isInfixOf` s :}
無名関数を使う例。
isInAny2 needle haystack = any (\s -> needle `isInfixOf` s) haystack
部分適用
:type dropWhile
dropWhile :: (a -> Prelude.Bool) -> [a] -> [a]
->の意味…左側の型を引数に取り、右側の型の値を返す関数。
Haskellにおいては、 すべての関数は1つしか引数を取らない 。 1つ渡すと型シグネチャから1つ削られたことがわかる。
:type dropWhile :type dropWhile isSpace map (dropWhile isSpace) [" a", "f", " e"]
dropWhile :: (a -> Prelude.Bool) -> [a] -> [a] dropWhile isSpace :: [Char] -> [Char] [“a”,“f”,“e”]
:type zip3 zip3 "foo" "bar" "quux"
zip3 :: [a] -> [b] -> [c] -> [(a, b, c)] [(’f’,’b’,’q’),(’o’,’a’,’u’),(’o’,’r’,’u’)]
:type zip3 "foo" let zip3foo = zip3 "foo" :type zip3foo zip3foo "aaa" "bbb"
zip3 “foo” :: [b] -> [c] -> [(Char, b, c)] GHCi> zip3foo :: [b] -> [c] -> [(Char, b, c)] [(’f’,’a’,’b’),(’o’,’a’,’b’),(’o’,’a’,’b’)]
関数が受け入れ可能な数よりも少ない数の引数を渡すことを関数の部分適用という(カリー化
)。
isInAny3 needle haystack = any (isInfixOf needle) haystack
カリー化の例。 使う前。
niceSum :: [Integer] -> Integer niceSum xs = foldl (+) 0 xs
完全に適用せずに、省略できる。
niceSum :: [Integer] -> Integer niceSum = foldl (+) 0
セクション
括弧で中置スタイルの関数の部分適用を書ける。
(1+) 2 map (*3) [24,36] map (2^) [3,5,7,9]
3 [72,108] [8,32,128,512]
:type (`elem` ['a'..'z'])
(`elem` [’a’..’z’]) :: Char -> Prelude.Bool
引数が小文字アルファベットか調べる関数になる。
(`elem` ['a'..'z']) 'a' (`elem` [1..9]) 1 (`elem` [1..9]) 10
True True False
all
と組み合わせる。簡潔にリスト全体か判定する関数になった。
all (`elem` ['a'..'z']) "Frobozz"
False
inInAny3
の改良版。
isInAny4 needle haystack = any (needle `isInfixOf`) haystack :type isInAny4
isInAny4 :: (Foldable t, Eq a) => [a] -> t [a] -> Prelude.Bool
アズパターン
:m +Data.List tail "foobar" tail (tail "foobar") tails "foobar" tails []
oobar obar [“foobar”,“oobar”,“obar”,“bar”,“ar”,“r”,“”] [[]]
空でない接尾辞が欲しくなったとする。
xs@(_:xs')
はアズパターンという。
変数 xs
を、 @
記号の右辺とマッチした値に束縛する、という意味。
:{ sufixes :: [a] -> [[a]] sufixes xs@(_:xs') = xs : sufixes xs' sufixes _ = [] :} sufixes "foo"
[“foo”,“oo”,“o”]
アズパターンはデータをコピーする代わりに共有する(xs
を再利用している)。
メモリの割り当てを回避するのでパフォーマンスが良い。
合成
init
関数はリストの最期の要素を除いた残りを返す。
init [1,2,3]
[1,2]
init
tails
を合成する。
suffix2 xs = init (tails xs) suffix2 [1,2,3]
[[1,2,3],[2,3],[3]]
ある関数を適用してから、その結果に別の関数をしている。このパターンを関数にできる。
:{ compose :: (b -> c) -> (a -> b) -> a -> c compose f g x = f (g x) suffixes3 xs = compose init tails xs :} suffixes3 [1,2,3]
[[1,2,3],[2,3],[3]]
勝手にカリー化するので変数は外せる。
suffixes4 = compose init tails suffixes4 [1,2,3]
[[1,2,3],[2,3],[3]]
連結はよくあることなので、 .
演算子として使える。
suffixes5 = init . tails suffixes5 [1,2,3]
[[1,2,3],[2,3],[3]]
通常の演算子のひとつ。
:type (.)
(.) :: (b -> c) -> (a -> b) -> a -> c
単語の先頭が大文字か。
:type isUpper . head isUpper 'W'
isUpper . head :: [Char] -> Prelude.Bool True
:type filter (isUpper . head)
filter (isUpper . head) :: [a1] -> [a2]
関数合成の例。
words
を使う。
:type words words "#define DLT_CHAOS 5"
words :: String -> [String] [“#define”,“DLT_CHAOS”,“5”]
tail
を使う。
:type tail tail ["#define","DLT_CHAOS","5"]
tail :: [a] -> [a] [“DLT_CHAOS”,“5”]
合成する。
:type tail . words (tail . words) "#define DLT_CHAOS 5"
tail . words :: String -> [String] [“DLT_CHAOS”,“5”]
head
を使う。
:type head . tail . words (head . tail . words) "#define DLT_CHAOS 5"
head . tail . words :: String -> String DLT_CHAOS
優先順位…ライブラリ関数の合成 > 畳み込み > 末尾再帰。
スペースリーク
遅延評価しない式のことを 正格な式
という。
seq
は正格化する。
:{ foldl' _ zero [] = zero foldl' step zero (x:xs) = let new = step zero x in new `seq` foldl' step new xs :}
seq
は値を評価する方法としての存在価値しかない。
:type seq
seq :: a -> b -> b
foldl' (+) 1 (2:[])
3
これは↓のように展開される。
:{ let new = 1 + 2 in new `seq` foldl' (+) new [] :}
3
一度↓のように展開する。
fold' (+) 3 []
seq
によってサンクがない。
型クラス
同値性検査をしたい。
色の場合。
:{ data Color = Red | Green | Blue colorEq :: Color -> Color -> Bool colorEq Red Red = True colorEq Green Green = True colorEq Blue Blue = True colorEq _ _ = False :}
stringの場合。
stringEq :: [Char] -> [Char] -> Bool stringEq [] [] = True stringEq (x:xs) (y:ys) = x == y && stringEq xs ys stringEq _ _ = False
つまり…型ごとに別の名前の関数を使って比較しなければならない。
すべて ==
で比較できると便利(ジェネリック関数)。
型クラスは実際に与えられたデータの型ごとに実装を持ち得る関数の集合によって定義するもの。
型クラスを定義する。 BasicEq
という型クラスを定義する。
インスタンスの型は a
。
:{ class BasicEq a where isEqual :: a -> a -> Bool :}
:type isEqual
isEqual :: BasicEq a => a -> a -> Bool
→ あらゆる型 a
に対して、 a
が BasicEq
のインスタンスである限り、 isEqual
は型 a
のパラメータを2つ取り、 Bool
を返す。
:{ instance BasicEq Bool where isEqual True True = True isEqual False False = True isEqual _ _ = False :}
:{ class BasicEq2 a where isEqual2 :: a -> a -> Bool isNotEqual2 :: a -> a -> Bool :}
notを追加する。
:{ class BasicEq3 a where isEqual3 :: a -> a -> Bool -- isEqual3 x y = not (isNotEqual3 x y) isNotEqual3 :: a -> a -> Bool -- isNotEqual3 x y = not (isEqual3 x y) :}
組み込みの Eq
型を見る。
class Eq a where (==), (/=) :: a -> a -> Bool -- 最低限の完全な定義は(==) か (/=) のどちらか x /= y = not (x == y) x == y = not (x /= y)
型クラスのインスタンス宣言
Color型だけでなく、BasicEq3のインスタンスとして宣言したあらゆる型に対して isEqual3
が使える。
instance BasicEq3 Color where isEqual3 Red Red = True isEqual3 Green Green = True isEqual3 Blue Blue = True isEqual3 _ _ = False
Show
:type show
show :: Show a => a -> String
show 1 show [1,2,3] show (1,2)
1 [1,2,3] (1,2)
putStrLn (show 1) putStrLn (show [1,2,3])
1 [1,2,3]
show "Hello!" putStrLn (show "Hello!") show ['H', 'i'] show "Hi, \"Jane\"" putStrLn (show "Hi, \"Jane\"")
\“Hello!\” Hello! \“Hi\” \“Hi, \\\”Jane\\\“\” Hi, \“Jane\”
自分で定義した型に対して Show
インスタンスを定義する。
instance Show Color where show Red = "Red" show Green = "Green" show Blue = "Blue"
Read
:type read
read :: Read a => String -> a
エラーが出てうまくいかない…。
:{ main = do putStrLn "Please enter a Double:" inpStr <- getLine let inpDouble = (read inpStr)::Double putStrLn ("Twice " ++ show inpDouble ++ " is " ++ show (inpDouble * 2)) :}
read "5"
Exception: Prelude.read: no parse
型の a
は、それぞれのReadのインスタンスのこと。
:type (read "5")
(read “5”) :: Read a => a
実際に呼ばれる特定の関数はreadの返り値から期待される型で決まる。
(read "5")::Integer (read "5")::Double
5 5.0
:{ instance Read Color where readsPrec _ value = -- ペアは文字列と欲しい返り値 tryParse [("Red", Red), ("Green", Green), ("Blue", Blue)] where tryParse [] = [] tryParse ((attempt, result):xs) = if (take (length attempt) value) == attempt then [(result, drop (length attempt) value)] else tryParse xs :}
なぜかstack overflowでできない。
(read "Red")::Color (read "Green")::Color (read "Blue")::Color (read "[Red]")::Color (read "[Red,Red,Blue]")::Color
Exception: stack overflow
シリアライズ
let d1 = [Just 5, Nothing, Nothing, Just 8, Just 9]::[Maybe Int] :type show :type show d1 putStrLn (show d1)
show :: Show a => a -> String show d1 :: String [Just 5,Nothing,Nothing,Just 8,Just 9]
ファイルに書き込み。
writeFile "test" (show d1)
再度読み込み。
d2
に明示的に型を与えるのがポイント。
input <- readFile "test" let d2 = (read input) ::[Maybe Int] print d1 print d2 d1 == d2
GHCi> [Just 5,Nothing,Nothing,Just 8,Just 9] [Just 5,Nothing,Nothing,Just 8,Just 9] True
showにほかの引数を与えてみる。
putStrLn $ show [("hi", 1), ("there", 3)]
[(“hi”,1),(“there”,3)]
putStrLn $ show [[1, 2, 3], [4, 0, 1], [], [503]]
[[1,2,3],[4,0,1],[],[503]]
putStrLn $ show [Left 5, Right "there", Left 0, Right "nine"]
[Left 5,Right “there”,Left 0,Right “nine”]
putStrLn $ show [Left 0, Right [1, 2, 3], Left 5, Right []]
[Left 0,Right [1,2,3],Left 5,Right []]
数値型
強力な数値型がある。 Haskellの演算子は関数にすぎない。 演算子を関数とみなす場合には括弧で囲む。
:{ data Color = Red | Green | Blue deriving (Read, Show, Eq, Ord) :} show Red (read "Red")::Color (read "[Red,Red,Blue]")::[Color] (read "[Red, Red, Blue]")::[Color] Red == Red Red == Blue Data.List.sort [Blue,Green,Blue,Red] Red < Blue
Red Red [Red,Red,Blue] [Red,Red,Blue] True False [Red,Green,Blue,Blue] True
型が参照している型が、やはりその型クラスのインスタンスであると宣言しないといけない。
CannotShow が Show のインスタンスではないので、エラー。
:{ data CannotShow = CannotShow data CannotDeriveShow = CannotDeriveShow CannotShow deriving (Show) :}
Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| Prelude Data.Char Data.List Data.Char Data.Char| <interactive>:7599:35-38: error: • No instance for (Show CannotShow) arising from the first field of ‘CannotDeriveShow’ (type ‘CannotShow’) Possible fix: use a standalone ’deriving instance’ declaration, so you can specify the instance context yourself • When deriving the instance for (Show CannotDeriveShow)
:{ data OK = OK instance Show OK where show _ = "OK" data ThisWorks = ThisWorks OK deriving (Show) :}
Haskell では異なる型の値を含むリストはサポートされてないので、JSONオブジェクトを直接表現できない。 代わりにデータ構成子でそれぞれの値を含む必要がある。
type JSONError = String class JSON a where toJValue :: a -> JValue fromJValue :: JValue -> Either JSONError a instance JSON JValue where toJValue = id fromJValue = Right
よりよいエラー文。
data Maybe a = Nothing | Just a deriving (Eq, Ord, Read, Show) data Either a b = Left a | Right b deriving (Eq, Ord, Read, Show)
instance JSON Bool where toJValue = JBool fromJValue (JBool b) = Right b fromJValue _ = Left "not a JSON boolean"
特殊なコメントで制限回避。プラグマというコンパイラ指令。
{-# LANGUAGE TypeSynonymInstances #-}
型クラスとインスタンス
新しいインスタンスはどこででも追加できる。型クラスを定義したモジュール内のみに限定されてない。 型クラスは開世界仮説に基づいている。
doubleToJValue :: (Double -> a) -> JValue -> Either JSONError a doubleToJValue f (JNumber v) = Right (f v) doubleToJValue _ _ = Left "not a JSON number" instance JSON Int where toJValue = JNumber . realToFrac fromJValue = doubleToJValue round instance JSON Integer where toJValue = JNumber . realToFrac fromJValue = doubleToJValue round instance JSON Double where toJValue = JNumber fromJValue = doubleToJValue id
インスタンスの重複。 リストをJSONの配列に変換する例。
instance (JSON a) => JSON [a] where toJValue = undefined fromJValue = undefined
instance (JSON a) => JSON [(String, a)] where toJValue = undefined fromJValue = undefined
{-# LANGUAGE FlexibleInstances #-} class Borked a where bork :: a -> String instance Borked Int where bork = show instance Borked (Int, Int) where bork (a, b) = bork a ++ ", " ++ bork b instance (Borked a, Borked b) => Borked (a, b) where bork (a, b) = ">>" ++ bork a ++ " " ++ bork b ++ "<<"
GHC は保守的で、使える可能性のあるインスタンスは1つだけであるべきとする。 なのでbork を使おうとするとエラーになる。
Tasks
TODO org-babelでhaskellを実行できなくなる
:set +t
を評価すると使えなくなる。
TODO xmpfilterのHaskell版を探す or 作る
なければ簡易版で作る。 いちいちロード、コンパイルは学習に不便。既存のがあるはずだが、調べ方がわからない。
org-babel
でよさそう。
Reference
ウォークスルー Haskell - Haskell 入門
わかりやすそうな入門。
A tutorial on the universality and expressiveness of fold
haskellの畳み込みのすぐれたチュートリアル。
I The Haskell 2010 Language
Haskellの仕様書。
CiteSeerX — The Design of a Pretty-printing Library
haskellのjsonプリティプリンタライブラリの設計。
IO入門編 - HaskellWiki
IOの説明。
Allure of the Stars
Haskellのブラウザローグライクゲーム。
モナドを理解する - 迷える者への手引き
Haskellのモナドの解説。