Haskell 入門 #4 | NANOG

Haskell 入門 #4

Wataru "Alt" Ohgai <alt@jj1lfc.dev>

2023/11/25 NANOG 言語勉強会

Haskell 入門 #4 | NANOG

前書き

本講は Haskell 初心者の Alt が教科書で学んだ内容をアウトプットするものです.
英語版原著を基に最新の情報を交えながら日本語で進めていきます.

教科書・参考・出典

クレジット

作成者: Alt <alt@jj1lfc.dev>
Special Thanks:

ライセンス: CC BY-NC-SA 3.0 Deed

Haskell 入門 #4 | NANOG

Types and Typeclasses

Believe the type

  • 型はコンパイル時に決定される
    • 型エラーはコンパイルの時点で全てキャッチされる
  • 型推論がある
    • 型を明示的に宣言しなくても動く
Haskell 入門 #4 | NANOG

型を確かめる

GHCi 上で :t <expr> と打つと <expr> の型を教えてくれる

ghci> :t 'a'
ghci> :t True
ghci> :t "HELLO!"
ghci> :t (True, 'a')
ghci> :t 4 == 5

型シグネチャ <expr> :: <type> として記述される

Haskell 入門 #4 | NANOG

関数の型

型推論があるものの,top level bind の関数はすべて自分で型を明示的に宣言する (型シグネチャを書く) のが良いとされる

04/type.hs

removeNonUppercase :: [Char] -> [Char]  -- 型シグネチャ
removeNonUppercase st = [ c | c <- st, c `elem` ['A'..'Z']]

関数 removeNonUppercase は引数 st[Char] をとり [Char] を返す関数

Haskell 入門 #4 | NANOG

複数の引数を取る関数の型

引数を 3 つ取る例

04/type.hs

addThree :: Int -> Int -> Int -> Int
addThree x y z = x + y + z
  • 引数の型と返値の型の書き方に区別はない
  • 一番右にあるのが返値の型

関数 addThree は引数 x y z にそれぞれ Int をとり Int を返す関数

Haskell 入門 #4 | NANOG

標準実装されたプリミティブなな型の例

  • Int
  • Integer
  • Float
  • Double
  • Bool
  • Char

※型は大文字で始まる

Haskell 入門 #4 | NANOG

Type variables

head 関数と fst 関数の型を見てみる

head :: [a] -> a

a: 型変数
多相関数 (polymorphic): 型変数を含む関数

fst :: (a, b) -> a

型変数は同時に複数取り得る
ab が同じ型でも OK

Haskell 入門 #4 | NANOG

Typeclasses 101

型クラス: 一定の振る舞いの型をまとめたインタフェース
オブジェクト指向におけるクラスとは異なる概念

(==) :: (Eq a) => a -> a -> Bool

=> の左に示されるのが型クラス制約で,この場合 Eq が型クラス
Eq クラスの型の引数を 2 つとり,Bool 型の返値を返す」

Haskell 入門 #4 | NANOG

標準実装されたプリミティブな型クラス

Eq: 等しさを検証する型クラス.

  • インスタンス (属している型): IO と関数を除く全ての標準実装された型
class Eq a where
  (==) :: a -> a -> Bool  -- メソッド
  (/=) :: a -> a -> Bool  -- メソッド
  {-# MINIMAL (==) | (/=) #-}
ghci> :t (==)
ghci> :t (/=)
ghci> :t elem
Haskell 入門 #4 | NANOG

Ord: 順序を含む型クラス.

  • インスタンス: Ordering, Int, Float, Double, Char, Bool など
class Eq a => Ord a where
  compare :: a -> a -> Ordering
  (<) :: a -> a -> Bool
  (<=) :: a -> a -> Bool
  (>) :: a -> a -> Bool
  (>=) :: a -> a -> Bool
  max :: a -> a -> a
  min :: a -> a -> a
  {-# MINIMAL compare | (<=) #-}
ghci> "Abrakadabra" < "Zebra"
ghci> "Abrakadabra" `compare` "Zebra"
ghci> 5 >= 2
ghci> 5 `compare` 3
Haskell 入門 #4 | NANOG

Show: 文字列として表現可能な型クラス.

  • インスタンス: Ordering, Integer, Int, Char, Bool など
class Show a where
  showsPrec :: Int -> a -> ShowS
  show :: a -> String
  showList :: [a] -> ShowS
  {-# MINIMAL showsPrec | show #-}
  • show 関数は Show 型クラスを引数にとり文字列を返す.
ghci> :t show
ghci> show 3
ghci> show 5.334
ghci> show True
Haskell 入門 #4 | NANOG

Read: 文字列からある型への変換を定義する型クラス.

  • インスタンス: Ordering, Integer, Int, Float, Double, Char, Bool など
class Read a where
  readsPrec :: Int -> ReadS a
  readList :: ReadS [a]
  GHC.Read.readPrec :: Text.ParserCombinators.ReadPrec.ReadPrec a
  GHC.Read.readListPrec :: Text.ParserCombinators.ReadPrec.ReadPrec
                             [a]
  {-# MINIMAL readsPrec | readPrec #-}
  • read 関数は文字列を引数にとり Read 型クラスを返す.
ghci> read "True" || False
ghci> read "8.2" + 3.8
ghci> read "5" - 2
ghci> read "[1,2,3,4]" ++ [3]
Haskell 入門 #4 | NANOG

read "4" を試す → 型推論でどの型を返すべきか不明なのでエラー

read 関数の型は

read :: (Read a) => String -> a

型アノテーション: <expr> :: <type> を使って明示的に型を指定することができる

ghci> read "5" :: Int
ghci> read "5" :: Float
ghci> (read "5" :: Float) * 4
ghci> read "[1,2,3,4]" :: [Int]
ghci> read "(3, 'a')" :: (Int, Char)
Haskell 入門 #4 | NANOG

Enum: シーケンシャルに順序が決まっている型クラス.

  • インスタンス: Ordering, Integer, Int, Char, Bool, (), Float, Double など
class Enum a where
  succ :: a -> a
  pred :: a -> a
  toEnum :: Int -> a
  fromEnum :: a -> Int
  enumFrom :: a -> [a]
  enumFromThen :: a -> a -> [a]
  enumFromTo :: a -> a -> [a]
  enumFromThenTo :: a -> a -> a -> [a]
  {-# MINIMAL toEnum, fromEnum #-}
ghci> ['a'..'e']
ghci> [LT .. GT]
ghci> [3 .. 5]
ghci> succ 'B'
Haskell 入門 #4 | NANOG

Bounded: 上限と下限が決まっている型クラス.

  • インスタンス: Ordering, Int, Char, Bool など
class Bounded a where
  minBound :: a
  maxBound :: a
  {-# MINIMAL minBound, maxBound #-}
ghci> minBound :: Int
ghci> maxBound :: Char
ghci> maxBound :: Bool
ghci> minBound :: Bool
ghci> maxBound :: (Bool, Int, Char)
Haskell 入門 #4 | NANOG

Num: 数字の型クラス.

  • インスタンス: Integer, Int, Float, Double など
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
  {-# MINIMAL (+), (*), abs, signum, fromInteger, (negate | (-)) #-}
ghci> 20 :: Int
ghci> 20 :: Integer
ghci> 20 :: Float
ghci> 20 :: Double
Haskell 入門 #4 | NANOG

Integral: 整数の型クラス.

  • インスタンス: Integer, Int など
class (Real a, Enum a) => Integral a where
  quot :: a -> a -> a
  rem :: a -> a -> a
  div :: a -> a -> a
  mod :: a -> a -> a
  quotRem :: a -> a -> (a, a)
  divMod :: a -> a -> (a, a)
  toInteger :: a -> Integer
  {-# MINIMAL quotRem, toInteger #-}
Haskell 入門 #4 | NANOG

Floating: 浮動小数点数の型クラス.

  • インスタンス: Float, Double
class Fractional a => Floating a where
  pi :: a
  exp :: a -> a
  log :: a -> a
  sqrt :: a -> a
  (**) :: a -> a -> a
  logBase :: a -> a -> a
  sin :: a -> a
  cos :: a -> a
  tan :: a -> a
  asin :: a -> a
  acos :: a -> a
  atan :: a -> a
  sinh :: a -> a
  cosh :: a -> a
  tanh :: a -> a
  asinh :: a -> a
  acosh :: a -> a
  atanh :: a -> a
  GHC.Float.log1p :: a -> a
  GHC.Float.expm1 :: a -> a
  GHC.Float.log1pexp :: a -> a
  GHC.Float.log1mexp :: a -> a
  {-# MINIMAL pi, exp, log, sin,
              cos, asin, acos, atan,
              sinh, cosh, asinh,
              acosh, atanh #-}
Haskell 入門 #4 | NANOG

補足: 型クラスが必要な理由

Q: 引数に文字列として表現可能な型の値を取り,文字列として返す関数の型を定義せよ.

型クラスのない世界での回答例 1:

showInt :: Int -> String
showInteger :: Integer -> String
showFloat :: Float -> String
showDouble :: Double -> String
...

型の数だけ異なる関数を作る必要がある → 失敗

Haskell 入門 #4 | NANOG

型クラスのない世界での回答例 2:

show' :: a -> String

文字列で表現できない自作データ型の値などを入れたときに動かない → 失敗

型クラスのある世界:

show :: Show a => a -> String

あらかじめ文字列で表せる型の集合に Show という名前をつける (型クラスとする) ことで型クラス制約を課すことができる