🕌

Haskell②型を信じろ! データ型,型クラス,型変数

2025/03/20に公開

Haskellの基本的な型と概念 -基本型から型クラス・型変数

前回はHaskellの概要と基本構文について書いた。
▶︎ [Book]すごいHaskellたのしく学ぼう①:概要と基本構文

Haskellには多くの組み込み型(基本的なデータ型)があり、型クラスがある。
今回はデータ型と型クラス、型変数についてまとめていく。


📚補足: これから型について学ぶために便利な...

さきに型の調べ方について、以下のコマンドを紹介する。

主要なGHCiコマンド一覧

コマンド 説明 使用例
:t <式> 指定した式の型を表示 :t map
:i <識別子> 関数・型・クラスの詳細情報を表示 :i Num
:k <型> 型のカインド(Kind)を表示 :k Maybe
:browse <モジュール名> モジュール内の関数や型を一覧表示 :browse Data.List
:doc <識別子> GHCi 9.0以降で関数のドキュメントを表示 :doc map

各コマンドの詳細解説

本題とはずれてしまうのでタブに納めますので興味あれば見てください〜!

上記コマンドの詳細解説

:t - 型の表示

:t(または:type)コマンドは、式の型を表示します。これは関数や値の型シグネチャを確認するのに最も頻繁に使用されるコマンドの一つです。

-- 基本的な使用例
ghci> :t True
True :: Bool

ghci> :t 'a'
'a' :: Char

ghci> :t map
map :: (a -> b) -> [a] -> [b]

-- 自分で定義した式の型も確認できる
ghci> :t \x -> x + 1
\x -> x + 1 :: Num a => a -> a

-- 複雑な式の型も表示可能
ghci> :t foldr (:) []
foldr (:) [] :: [a] -> [a]

:i - 詳細情報の表示

:i(または:info)コマンドは、関数、型、クラスに関するより詳細な情報を表示します。型シグネチャだけでなく、型クラスのインスタンス、メソッド、定義などの情報も表示します。

-- 関数の情報
ghci> :i map
map :: (a -> b) -> [a] -> [b]  -- 型シグネチャ
        -- Defined in 'GHC.Base'  -- 定義場所

-- 型の情報
ghci> :i Int
data Int = GHC.Types.I# GHC.Prim.Int#  -- 定義
instance Eq Int       -- インスタンス
instance Ord Int
instance Show Int
instance Read Int
instance Enum Int
instance Num Int
...

-- 型クラスの情報
ghci> :i Num
class Num a where  -- クラス定義
  (+) :: a -> a -> a
  (-) :: a -> a -> a
  (*) :: a -> a -> a
  negate :: a -> a
  abs :: a -> a
  signum :: a -> a
  fromInteger :: Integer -> a
        -- Defined in 'GHC.Num'
instance Num Integer  -- インスタンス一覧
instance Num Int
instance Num Float
instance Num Double
...

:k - カインドの表示

:k(または:kind)コマンドは、型のカインド(Kind)を表示します。カインドは「型の型」と考えることができ、型がどのように他の型と組み合わさるかを示します。

-- 基本的な型のカインド
ghci> :k Int
Int :: *
-- * は具体的な型(データを持つことができる型)を表す

-- 型コンストラクタのカインド
ghci> :k Maybe
Maybe :: * -> *
-- Maybe は型引数を1つ取り、具体的な型を返す型コンストラクタ

-- 複数の型引数を取る型コンストラクタ
ghci> :k Either
Either :: * -> * -> *
-- Either は型引数を2つ取り、具体的な型を返す型コンストラクタ

-- 型クラスのカインド
ghci> :k Eq
Eq :: * -> Constraint
-- Eq は型引数を1つ取り、型制約を返す

:browse - モジュール内容の表示

:browseコマンドは、指定したモジュール内のすべての関数、型、クラスなどを一覧表示します。

-- Data.Maybeモジュールの内容を表示
ghci> :browse Data.Maybe
catMaybes :: [Maybe a] -> [a]
fromJust :: Maybe a -> a
fromMaybe :: a -> Maybe a -> a
isJust :: Maybe a -> Bool
isNothing :: Maybe a -> Bool
listToMaybe :: [a] -> Maybe a
mapMaybe :: (a -> Maybe b) -> [a] -> [b]
maybeToList :: Maybe a -> [a]
maybe :: b -> (a -> b) -> Maybe a -> b

-- Data.Listモジュールの一部を表示
ghci> :browse Data.List
...
sort :: Ord a => [a] -> [a]
sortBy :: (a -> a -> Ordering) -> [a] -> [a]
...

:doc - ドキュメントの表示

:docコマンドは、GHCi 9.0以降で使用可能で、関数や型のドキュメントを表示します。

-- mapのドキュメントを表示
ghci> :doc map
map :: (a -> b) -> [a] -> [b]
  Map a function over all the elements of a list.

-- Maybeのドキュメントを表示
ghci> :doc Maybe
data Maybe a = Nothing | Just a
  The Maybe type encapsulates an optional value. A value of type
  Maybe a either contains a value of type a (represented as Just a),
  or it is empty (represented as Nothing).
Haskellにおけるカインド(Kind)とは?

📚Haskellにおけるカインド(Kind)とは?

カインド(Kind)は「型の型」であり、型がどのように他の型と組み合わさるかを記述するシステムです。カインドは、型システムにおける型の役割を表します。

基本的なカインド

カインド表記 説明
* または Type 具体的な型(型引数を取らない完全な型) Int, Bool, Char
* -> * 型引数を1つ取る型コンストラクタ Maybe, [] (リスト)
* -> * -> * 型引数を2つ取る型コンストラクタ Either, (,) (ペア)
* -> Constraint 型クラス(型引数を1つ取り、制約を返す) Eq, Show, Ord

カインドの例と解説

-- 具体的な型のカインド
:k Int          -- Int :: *
:k Bool         -- Bool :: *
:k [Int]        -- [Int] :: *  (特定の型のリストは具体的な型)

-- 型コンストラクタのカインド
:k []           -- [] :: * -> *  (リスト型コンストラクタ)
:k Maybe        -- Maybe :: * -> *  (Maybeは型引数を1つ取る)
:k Either       -- Either :: * -> * -> *  (Eitherは型引数を2つ取る)

-- 型クラスのカインド
:k Eq           -- Eq :: * -> Constraint
:k Monad        -- Monad :: (* -> *) -> Constraint  (MonadはMaybeのような型コンストラクタに適用される)

-- 型引数を適用した結果
:k Maybe Int    -- Maybe Int :: *  (型引数を適用すると具体的な型になる)
:k Either Int   -- Either Int :: * -> *  (1つだけ適用すると残り1つの型引数を取る型コンストラクタになる)
:k Either Int Bool  -- Either Int Bool :: *  (両方適用すると具体的な型になる)

カインドシステムの重要性

  1. 型の安全性: カインドシステムにより、型レベルでの不適切な適用を防ぎます
  2. 高階型: 型コンストラクタを別の型コンストラクタに適用するような高度な型レベルプログラミングを可能にします
  3. 型クラスの制約: どの型がどの型クラスのインスタンスになれるかを制限します

カインドは型レベルプログラミングやより高度なHaskellの機能を理解する上で重要な概念です。特に、型クラスやモナド、型族(type families)などの高度な機能を扱う際に理解が必要になります。

📚基本データ型

📖 数値型

型名 説明 特徴 使用例
Int 固定幅整数型 有界(通常32ビットで±21億程度) カウンタ、インデックス
Integer 任意精度整数型 無制限(メモリが許す限り) 大きな数値計算
Float 単精度浮動小数点数 精度約7桁 簡易的な数値計算
Double 倍精度浮動小数点数 精度約15桁 科学計算、精度が必要な計算

🌱 Int型の例

minInt :: Int
minInt = -2147483648

-- 一般的な計算
sum :: Int
sum = 42 + 58  -- 100

🌱 Integer型の例

factorial :: Integer -> Integer
factorial n = product [1..n]

-- 非常に大きな数値を扱える
-- factorial 50 = 30414093201713378043612608166064768844377641568960512000000000000

🌱 Float型の例

circumference :: Float -> Float
circumference r = 2 * pi * r

-- ghci> circumference 4.0
-- 25.132742

🌱 Double型の例

circumference' :: Double -> Double
circumference' r = 2 * pi * r

-- ghci> circumference' 4.0
-- 25.132741228718345  -- Floatより精度が高い

📖 文字と論理値

型名 説明 表記法
Char 単一文字 シングルクォート 'a', '5', '$'
Bool 論理値 True/False True, False
String 文字列([Char]のシノニム) ダブルクォート "Hello"

📖 複合型

型名 説明 特徴
[a] (リスト) 同じ型の要素のコレクション 同種要素のみ、可変長
(a, b, ...) (タプル) 複数の値をグループ化 異なる型可、固定長
() (ユニット) 値を持たない型 副作用のみの関数の戻り値など

📚型クラス (Typeclass)

型クラスは、特定の振る舞いを定義するインターフェースのようなもの。
("共通の振る舞いを持つ型のグループ"とも言える。)

上記で記載したような型が、型クラスのメンバーであるということは、
その型がその型クラスの定義する振る舞いをサポートし実装していることを意味する。

型クラス 主な機能・メソッド
Eq 等値比較:(==), (/=)
Ord 順序比較:(<), (<=), (>), (>=), compare, max, min
Show 文字列表現:show
Read 文字列からの変換:read
Num 数値演算:(+), (-), (*), negate, abs, signum
Integral 整数演算:div, mod, quotRem, toInteger
Floating 浮動小数点演算:pi, exp, log, sin, cos など
Enum 列挙:succ, pred, toEnum, fromEnum, 範囲記法[a..b]
Bounded 上限・下限値:minBound, maxBound
Functor 関数適用の抽象化:fmap, (<$>)
Applicative 適用子パターン:pure, (<*>)
Monad シーケンシャルな計算:(>>=), (>>), return

📖 代表的な型クラス

🌱 Eq型クラス

等価性テストをサポートする型のための型クラス。

実装する関数 説明
== 等しいかどうか 5 == 5True
/= 等しくないかどうか 5 /= 3True
-- Eq型クラスのメンバー同士を比較できる
"Hello" == "Hello"  -- True
"Hello" /= "World"  -- True
(1, 'a') == (1, 'a')  -- True

ほとんどの標準型(関数とIO型を除く)はEq型クラスのメンバーだ

🌱 Ord型クラス

順序付けが可能な型のための型クラス。

実装する関数 説明
>, <, >=, <= 比較演算子 5 > 3True
compare 順序比較(GT, LT, EQを返す) 5 compare 3GT
"Abrakadabra" < "Zebra"  -- True
5 `compare` 3            -- GT

※Ord型クラスのメンバーになるには、まずEq型クラスのメンバーである必要がある。

🌱 Show型クラス

文字列として表現できる型のための型クラス。

show 3      -- "3"
show True   -- "True"
show [1,2,3]  -- "[1,2,3]"

Read型クラス

Showの逆で、文字列から特定の型の値を生成する型クラス。

read "5" :: Int       -- 5
read "5" :: Float     -- 5.0
read "[1,2,3,4]" :: [Int]  -- [1,2,3,4]
read "(3, 'a')" :: (Int, Char)  -- (3, 'a')

型注釈(:: 型)が必要な場合が多い。
型注釈がないと、Haskellは結果の型を決定できないことがある。

その他の重要な型クラス

型クラス 説明 主なメンバー 主な関数
Enum 順序付けられた型 Int, Char, Bool succ, pred, [a..b]
Bounded 上限と下限を持つ型 Int, Char, Bool minBound, maxBound
Num 数値的振る舞いをする型 Int, Integer, Float, Double +, -, *
Integral 整数型 Int, Integer div, mod
Floating 浮動小数点数型 Float, Double sin, cos, sqrt
-- Enum型クラスの例(範囲表記)
[1..5]       -- [1,2,3,4,5]
['a'..'e']   -- "abcde"

-- Bounded型クラスの例
minBound :: Int  -- -2147483648
maxBound :: Bool  -- True

-- Num型クラスの例
(5 :: Int) + (10 :: Int)      -- 15
(5.5 :: Double) * (2 :: Double)  -- 11.0

-- Integral型クラスの例
7 `div` 2    -- 3(整数除算)
7 `mod` 2    -- 1(剰余)

-- Floating型クラスの例
sin (pi / 2)  -- 1.0
sqrt 16.0     -- 4.0

📖 基本データ型と型クラスのインスタンスまとめ

データ型 Eq Ord Show Read Num Integral Floating Enum Bounded Functor Applicative Monad
Int
Integer
Float
Double
Char
Bool
[a]* ✓* ✓* ✓* ✓*
Maybe a* ✓* ✓* ✓* ✓*
Either a b* ✓* ✓* ✓* ✓*
(a,b)* ✓* ✓* ✓* ✓*
IO a
  • これらの型がEq, Ord, Show, Readのインスタンスになるためには、
    型パラメータもそれぞれの型クラスのインスタンスである必要がある。

📚型変数 (Type Variables)

型変数は任意の型を表すためのプレースホルダーで、
Haskellの多相型システムの基礎となるものだ。

📖 型変数の特徴

  1. 小文字で表記: 具体的な型名(IntStringなど)は大文字で始まるのに対し、型変数は小文字(通常 a, b, c など)で表記されます。

  2. 多相性を実現: 同じ関数が複数の型で動作可能になります。

  3. ジェネリクスと類似: オブジェクト指向言語のジェネリクスに似た概念ですが、より強力で言語に自然に統合されています。

📖 型変数の使用例

-- 任意の型aのリストの先頭要素を返す
head :: [a] -> a

-- 任意の型aと型bのペアから最初の要素を取り出す
fst :: (a, b) -> a

-- 任意の型a, b, cのトリプルを受け取り、2番目の要素を返す
getSecond :: (a, b, c) -> b
getSecond (_, y, _) = y

📖 型クラス制約付きの型変数

型変数に型クラスの制約を付けることができる。
これにより、「任意の型だが、特定の振る舞いができる型」を表現できます。

-- Eq型クラスに属する任意の型aについて、
-- aの値が与えられたリストに含まれるかを判定
elem :: Eq a => a -> [a] -> Bool

-- Ordに属する任意の型aについて、2つの値の大きい方を返す
max :: Ord a => a -> a -> a

-- Numに属する任意の型aについて、2つの値の積を計算
multiply :: Num a => a -> a -> a
multiply x y = x * y

型クラス制約は型シグネチャで => の前に記述します。複数の制約がある場合は括弧で囲みカンマで区切ります。

-- aはShowとEqの両方のメンバーである必要がある
showAndCompare :: (Show a, Eq a) => a -> a -> String
showAndCompare x y = show x ++ " is equal to " ++ show y ++ ": " ++ show (x == y)

📖 型変数とジェネリクスの比較

Haskellの型変数はジェネリクスと似ていますが、いくつかの点でより強力です:

  1. 言語の根幹部分: 後付けの機能ではなく、型システムの中核

  2. 高階多相性: 関数の型にも型変数を使用可能

  3. 型クラス制約: 型変数に制約を付けられる

-- ジェネリクスのような基本的な使い方
identity :: a -> a
identity x = x

-- 高階多相性の例(関数を引数に取る)
applyTwice :: (a -> a) -> a -> a
applyTwice f x = f (f x)

-- 型クラス制約の例
sortList :: Ord a => [a] -> [a]
sortList = sort

📚型変換

Haskellでは、型間の変換が必要な場合があります。以下に一般的な変換関数を示します。

関数 説明
fromIntegral 整数型から別の数値型へ変換 fromIntegral (length [1,2,3]) + 3.14
round, floor, ceiling 浮動小数点数から整数へ変換 round 3.74
show 値を文字列に変換 show 42"42"
read 文字列から値に変換 read "42" :: Int42

📚特殊な型

Haskellには、特定のプログラミングパターンをサポートするための特殊な型がある。

型名 説明 使用例
Maybe a 値が存在するかどうかを表現 エラーハンドリング、オプショナルな値
Either a b 2つの型のうちどちらかを表現 エラー処理、二者択一の結果
[a] リスト型(同じ型の複数の値) コレクション、シーケンス
IO a 入出力操作を表す型 ファイル操作、ユーザー入力
-- Maybeの例(値が存在しない可能性を表現)
safeDiv :: Int -> Int -> Maybe Int
safeDiv _ 0 = Nothing
safeDiv x y = Just (x `div` y)

-- Eitherの例(エラーメッセージ付きの結果)
safeDiv' :: Int -> Int -> Either String Int
safeDiv' _ 0 = Left "Division by zero"
safeDiv' x y = Right (x `div` y)

Discussion