Haskell

概要

Haskellは純粋関数型Programming Language

Memo

QuickCheck

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行ごとで新しいセッションになってしまうよう。 :{}: を使って複数行評価されるようにする。

変数名 xsx の複数形という意味。

式指向のため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 languageではこう書く。

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 languageunion と同じ。

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

便利な条件判定、 allany

: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の型システムのうえでは難しい。 複数のリストを閉じ合わせるには zip3zip7` を使う。

リストのいけてる扱い方

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 languageの例。 こんなふうにはできないので末尾再帰を使う。

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 に対して、 aBasicEq のインスタンスである限り、 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のブラウザローグライクゲーム。

Archives

DONE すごいHaskellたのしく学ぼう [100%]

何か作る的なテーマがないので読むのがつらい。 参考になりそうなパッケージを探したけど、ピンとくるものがない。 いくつかパッケージをbuildしてみたが、依存パッケージで動かない。どうするかな。

よくわからん。