この章では、タプル、列挙型、直積型、直和型を紹介しています。
タプル (tuple) は、違うタイプ (type、型)のデータを複数保持できるデータ構造です。タプルに含まれる個々のデータのことを要素 (element)と呼びます。
リストが同じタイプのデータしか保持できないのに対して、タプルは違うタイプのデータを保持できる点が大きな違いです。
また、リストは、内包する要素の型が同じであれば、要素数が違っても同じ型としてみなされますが、タプルは要素数が違うものは違う型とみなされます。
タプルはデータの全体を ( と ) で囲んで、各要素を , (カンマ)で区切って作ります。
タプルを使うと関数の戻り値として複数の値を返すことができるようになります。
addsub x y = (x + y, x - y)
main = do
print $ addsub 10 5
(15,5)
タプルは要素を分割して受け取ることも可能です。
addsub x y = (x + y, x - y)
a = addsub 10 5
(a1, a2) = addsub 10 5
main = do
print a
print a1
print a2
(15,5)
15
5
main = do
let t = (10, 20)
print $ fst t
print $ snd t
10
20
要素が3以上のタプルから要素を取り出すには変数を使います。
main = do
let t = (10, 20, 30)
let (x,y,z) = t
print x
print y
print z
print t
10
20
30
(10,20,30)
リストと違ってタプルには !!
は使えません。
a = zip [1,2,3,4,5]["one","two","three"]
main = do
print $ a!!0
print $ a!!1
print $ a!!2
print a
1,"one")
(2,"two")
(3,"three")
[(1,"one"),(2,"two"),(3,"three")]
リストのどちらかの要素数が多い場合は、余った要素は無視されます。
列挙型、直積型、直和型を合わせて、代数的データ型と呼びます。
ここでは、まず、列挙型の紹介から始めます。
data Color = Red | Green | Blue
上記のコードで Color
は型と呼ばれ、Red
と Green
と Blue
はコンストラクタと呼ばれます。
型とコンストラクタは大文字で始める決まりになっています。このことは慣習ではなく、言語仕様で決まっています。
自分で定義した型をprint
するには、「deriving
」宣言を使ってShow
型クラスを追加しなければなりません。
data Color = Red | Green | Blue deriving Show
main = do
print Blue
putStrLn $ show Green
Blue
Green
deriving
を使うことによって、自分で定義した型に機能が追加ですます。Show
という型クラスにはshow
という関数が含まれています。show
関数は、値を文字列に変換します。print
は、自動的にshow
関数を呼び出しますが、putStr
とputStrLn
は明示的にshow
関数を呼び出さなければなりません。
Enum
Enum
型クラスを追加すれば、列挙型を数値に変換することができるようになります。
data Color = Red | Green | Blue deriving (Show, Enum)
main = do
print $ fromEnum Red
print $ fromEnum Green
print $ fromEnum Blue
print ( toEnum 0 :: Color )
print ( toEnum 1 :: Color )
print ( toEnum 2 :: Color )
0
1
2
Red
Green
Blue
deriving
で複数の型クラスを追加するには括弧 ( ) で囲んで ,
(カンマ)で区切ります。
fromEnum
で列挙型を数値に変換し、toEnum
では::
で変換する型を指定して、数値を列挙型に変換します。
優先順位の都合上、::
が含まれる場合は$
ではなく、括弧で囲む必要があります。
Bool
真偽値を表すBool
型も、標準ライブラリで定義されている列挙型です。
直積型は、一つのデータの中に、違う型のデータを保持できるフィールドを複数持てる型です。
data Person = Person String Int deriving Show
上記のコードで、1つ目のPerson
は、型です。2つ目のPerson
はコンストラクです。型とコンスラクタは同じ名前でも違う名前でも構いません。String
とInt
は、フィールドの型になります。
data Person = Person String Int deriving Show
main = do
let a = Person "John" 25
let b = Person "Mary" 20
let (Person str1 int1) = a
let (Person str2 int2) = b
print (str1, int1)
print (str2, int2)
("John",25)
("Mary",20)
レコード構文という方法を使えば、直積型や直和型のフィールドに名前をつけて呼び出すことができます。
data Person = Person { name :: String, age :: Int } deriving Show
main = do
let a = Person "John" 25
let b = Person "Mary" 20
print (name a, age a)
print (name b, age b)
putStrLn (name a)
putStrLn (name b)
("John",25)
("Mary",20)
John
Mary
フィールドが1つだけの直積型は newtype
というキーワードで定義できます。
newtype Score = Score Int deriving Show
a = Score 100
main = do
let (Score score) = a
print score
100
newtype
で定義されたデータは、data
で定義されたデータよりも高速で動作するらしいです。
直和型は、列挙型の各データにフィールドを持たせて、複数の直積型を定義したものです。
data IntStr = DataInt Int | DataStr String deriving Show
foo (DataInt 1 ) = "hello"
foo (DataStr "1") = "world"
foo _ = "?"
main = do
print $ foo $ DataInt 0
print $ foo $ DataInt 1
print $ foo $ DataStr "0"
print $ foo $ DataStr "1"
"?"
"hello"
"?"
"world"
通常関数は、同一タイプの引数しか受け付けませんが、この例だと、複数のタイプの引数を受け取れるようになっているとのことらしいです。私自身がイマイチわかっていません。
String
は文字列を保持できるデータ型です。文字列は文字のリストです。String
という名前は、文字のリスト[Char]
に型シノニムという方法でをつけた別名です。
type String = [Char]