Haskellerへの道 #13 - ファンクター

Last Edited: 6/26/2024

このブログ記事では、Haskellの重要な型クラス、ファンクターを紹介します。

Haskell & Functor

ファンクター (Functor) タイプクラス

Haskell の最も重要な概念の一つであり、多くの人が混乱するのがモナド (Monad) です。難しいのは、多くの人が急ぎすぎて、 できるだけ早く理解しようとするからです。ここでは、異なるアプローチを取り、モナドを真に理解するための前提条件をゆっくりと 確実に学んでいきます。その前提条件の一つがファンクターです。ファンクターは、ちょうど 1 つの型を持つ型コンストラクタに対して 適用できる型クラスです。型コンストラクタは、1 つ以上の型をパラメータとして取り、具体的な型を生成する関数です。以下は、 型コンストラクタ f に対してファンクターのインスタンスを作成する方法です。

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

型コンストラクタをファンクター型クラスの有効なインスタンスにするには、fmap を定義するだけで良いです。 fmap は、関数 (a -> b) を取り、型コンストラクタ内の型の値に適用します。これは具体例がないと理解しづらいかもしれませんので、 いくつかの例を見てみましょう。

リスト

リストは、IntFloatString などの型を取り、新しい型 [Int][Float][String] を生成する型コンストラクタです。 リストもファンクターであり、ファンクター型クラスの有効なインスタンスです。実際、map はリストに対してのみ機能する fmap です。

instance Functor [] where
    fmap = map
 
map :: (a -> b) -> [a] -> [b]

map 関数は関数を取り、リスト内の要素に適用します。これは fmap と同じことです。したがって、以下のようにすることができます。

ghci> map (*2) [1..3]
[2,4,6]
ghci> fmap (*2) [1..3]
[2,4,6]
ghci> map (++"!") ["Hello", "Nice to meet you"]
["Hello!","Nice to meet you!"]
ghci> fmap (++"!") ["Hello", "Nice to meet you"]
["Hello!","Nice to meet you!"]

Maybe

もう一つの型コンストラクタ/ファンクターの例は Maybe で、IntFloatString などの型を取り、 新しい型 Maybe IntMaybe FloatMaybe String を生成します。以下は、Maybe がファンクター型クラス のインスタンスである方法です。

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

異なる値コンストラクタ JustNothing に対して fmap を定義するためにパターンマッチングを使用しています。 Nothing は空なので、出力も Nothing になります。一方、Just では中の値に関数 f を適用します。 これにより、fmap 関数を Maybe に対して使用して、以下のようにすることができます。

ghci> fmap (*2) (Just 4)
Just 8
ghci> fmap (*2) Nothing
Nothing
ghci> fmap (++"!") (Just "Hello")
"Hello!"
ghci> fmap (++"!") Nothing
Nothing

IO

IO も実際には型コンストラクター/ファンクターであり、 (IO (), IO String, IO Float, etc.) 対応する fmap 関数があります。

instance Functor IO where
    fmap f action = do
        result <- action
        return (f result)

これは IO アクションから値を result にバインドし、関数 fresult に適用し、その値を持つ IO アクションを return を使用して作成します。 したがって、以下のようにすることができます。

ghci> fmap (++"!") getLine
Hello
"Hello!"

上級者用チャレンジ: (->) r もファンクターの有効なインスタンスです。fmap がどのように定義されているかを考えてみてください。(ヒント: ->r -> a のように型定義で使用され、2つの型をパラメータとして取り、関数の新しい型を生成する型コンストラクタでもあります。)

クイズ

この記事では、学習した内容を確認するためのクイズを設けます。記事のメイン部分を読んだ後に、ぜひ自分で問題を解いてみることを強くお勧めします。各問題をクリックすると答えが表示されます。

リソース