Haskellの言語機構整理

はじめに

「すごいHaskellたのしく学ぼう!」を読んだ。Haskellは完全に初体験。本の中で紹介されていたHaskellの機能で、印象的だったものを整理しておく。

セクションタイトルの「基本的」「発展的」は自分独自の主観的な分類。それ単体で完結する単純な機能は基本に入れている。一方で自分が初見で理解しづらいと感じ、概念そのものを受け入れるのに時間がかかったものは発展の方に入れている。

基本的な機能

リスト内包表記

[x * 2 | x <- 1..5]

> [2, 4, 6, 8, 10]

パターンマッチ

factorial :: Int -> Int
factorial 0 = 1
factorial n = n * factorial(n-1)

ガード

factorial :: Int -> Int
factorial n
    | factorial 0 = 1
    | factorial n = n * factorial(n-1) 

where節

calcBmis :: [(Double, Double)] -> [Double]
calcBmis xs = [bmi x h | (x, h) <- xs]
    where bmi weight height = weight / height ^ 2

let式

4 * (let a = 9 in a + 1) + 2

> 42

let で値を名前に束縛し、in の中の式が評価される。

case式

describeList :: [a] -> String
describeList ls = "The list is "
                     ++ case ls of [] -> "empty."
                                   [x] -> "a singleton list."
                                   xs -> "a longer list."

ラムダ式

map (\(a, b) -> a + b) [(1, 2), (3, 5)]

> [3, 8]

型シノニム

type IntMap v = Map Int v
-- 以下の定義と同じ
type IntMap = Map Int

発展的な機能

関数適用演算子

定義

($) :: (a -> b) -> a -> b
f $ x = f x

使い方

sum(filter(>10) (map (*2) [2..10]))
-- 以下のように書き換えられる
sum $ filter(>10) (map (*2) [2..10])
sum $ filter(>10) $ map (*2) [2..10]

右側の式を左側の関数の引数として、括弧を減らすことができる。

関数合成

定義

(*) :: (b -> c) -> (a -> b) -> a -> c
f . g -> \x -> f (g x)

使い方

map (\x -> negate (abs x)) [5, -3, -6]
-- 以下のように書き換えられる
map (negate . abs) [5, -3, -6]

データ型の定義

data Shape Circle Float Float Float |
         Rectangle Float Float Float Float
    deriving(Show) -- データ型のインスタンスを導出

-- レコード構文(フィールドに名前を与える)
data Car = { company :: String
                   , model :: String
                   , year :: Int
                   } deriving(Show)

型コンストラク

-- aは型引数
data Maybe a = Nothing | Just a

型クラスの定義

- a は型変数
class Eq a where
    -- 関数の型定義
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool
    -- 関数のデフォルト実装(必須でない)
    (==) = not(x /= y)
    (/=)  = not(x /= y)

型変数は対象クラス(今回はEq)のインスタンスになる型。型引数とは別。

インスタンスの定義

instance Eq TrafficLight where
    Red == Red = True
    Blue == Blue = True
    Yellow == Yellow = True
    _ == _ = False

-- インスタンスになるデータ型の定義
data TrafficLight = Red | Blue | Yellow

ここで言葉の整理をしておく。

  • 型クラス
    • 振る舞いを定義するインターフェース(振る舞い=メソッド)
  • インスタンス
    • 型クラスの振る舞いを実装したデータ型
    • 「データ型Aが型クラスBに属している」=「型Aは型クラスBのインスタンスである」

Functor型クラス

定義

class Functor f where
    fmap :: (a -> b) -> fa -> fb

使い方

instance Functor Maybe where
  fmap f (Just x) = Just(f x)
  fmap f Nothing = Nothing

Applicative型クラス

定義

class (Functor f) => Applicative f where
    pure :: a -> f a
    (<*>) :: f(a -> b) -> f a -> f b

使い方

instance Applicative Maybe where
    pure = Just
    Nothing <*>_ = Nothing
    (Just f) <*> something = fmap f something

Monad型クラス

定義

class Monad m where
    return :: a -> m a
    (>>=) :: m a -> (a -> m b) -> m b -- 別名: バインド
    (>>) :: m a -> m b -> m b
    x >> y = x >>= \_ -> y
    -- ユーザーがfailを呼ぶことはない
    fail :: String -> m a
    fail msg = error msg

使い方

instance Monad Maybe where
    return x = Just x
    Nothing >>= f = Nothing
    Just x >>= f = f x

ここで Functor, Applicative, Monad の整理をしておく。

入力 出力 適用関数 実装メソッド
Functor f a f b a -> b fmap :: (a -> b) -> f a -> fb
Applicative f a f b f(a -> b) pure :: a -> f a
(<*>) :: f(a -> b) -> f a -> fb
Monad m a m b a -> m b return :: m -> m a
(>>=) :: a -> m b -> m a -> m b

<$> (fmapと等価な中置演算子)

定義

(<$>) :: (Functor f ) => (a -> b) -> f a -> f b
f <$> x = fmap f x 

使い方

pure f <*> x <*> y
-- 以下のように書き換えられる
fmap f x <*> y
f <$> x <*> y

do記法

marySue :: Maybe Bool
marySue = Just 9 >>= (\x -> Just(x > 8))
-- 以下のように書き換えることができる
marySue = do 
    x <- Just 9
    Just (x > 8)

参考