HaskellにおけるContravariantファンクタの理解

contravariant モジュールの導入

contravariantパッケージをインストールするには以下のコマンドを実行します。

$ cabal install contravariant

モジュールをインポートするには以下のように記述します。

Prelude> import Data.Functor.Contravariant

Contravariant ファンクタ(逆変函子)

Contravariantクラスは以下のように定義されています。

class Contravariant f where
  contramap :: (a -> b) -> f b -> f a
  (>$) :: b -> f b -> f a
  (>$) = contramap . const

標準的なFunctor(ファンクタ)は共変(covariant)であり、Covariant Functorの略称として使用されます。Functorが共変と呼ばれる理由は、関数の出力側(positive位置)を変換するからです。

一方、Contravariantクラスは逆変(contravariant)であり、Contravariant Functorの略称です。Contravariantが逆変と呼ばれる理由は、関数の入力側(negative位置)を変換するからです。

Contravariant の法則

以下の法則を満たす必要があります。

1. contramap id = id
2. contramap f . contramap g = contramap (g . f)

Predicate と Contravariant

Predicate型はContravariantの代表的な例です。

newtype Predicate a = Predicate { runPredicate :: a -> Bool }

instance Contravariant Predicate where
  contramap f p = Predicate $ runPredicate p . f

Predicateは a -> Bool 型の関数をラップしています。

法则の証明

1. contramap id = id

contramap id p
= Predicate $ runPredicate p . id
= Predicate $ runPredicate p
= p
= id p

2. contramap f . contramap g = contramap (g . f)

(contramap f . contramap g) p
= contramap f (contramap g p)
= contramap f (Predicate $ runPredicate p . g)
= Predicate $ runPredicate (Predicate $ runPredicate p . g) . f
= Predicate $ (runPredicate p . g) . f
= Predicate $ runPredicate p . g . f

contramap (g . f) p
= Predicate $ runPredicate p . (g . f)
= Predicate $ runPredicate p . g . f

実用例

import Data.Functor.Contravariant

isAdult :: Predicate Int
isAdult = Predicate (>= 18)

ageCategory :: Predicate String
ageCategory = contramap length isAdult

nameLengthValid :: Predicate Int
nameLengthValid = contramap length isAdult

nameToLength :: Int -> String
nameToLength 3 = "短"
nameToLength 5 = "中"
nameToLength 8 = "長"
nameToLength _ = "不明"

sampleNames :: [String]
sampleNames = ["Tom", "Alice", "Bob", "Carolina", "Alexander"]

main :: IO ()
main = print $ filter (runPredicate ageCategory) sampleNames
-- 出力: ["Carolina","Alexander"]

Comparison と Contravariant

Comparison型もContravariantインスタンスを持ちます。

newtype Comparison a = Comparison { getCompare :: a -> a -> Ordering }

instance Contravariant Comparison where
  contramap f cmp = Comparison $ on (getCompare cmp) f

ここで on 関数は以下のように定義されています。

on :: (b -> b -> c) -> (a -> b) -> a -> a -> c
on (*) f = \x y -> f x * f y

法则の証明

1. contramap id = id

contramap id c
= Comparison $ on (getCompare c) id
= Comparison $ \x y -> getCompare c (id x) (id y)
= Comparison $ \x y -> getCompare c x y
= c
= id c

2. contramap f . contramap g = contramap (g . f)

証明はPredicateと同様の手順で示すことができます。

実用例

import Data.Functor.Contravariant
import Data.List

words :: [String]
words = ["Haskell", "Rust", "Python"]

main :: IO ()
main = do
  print $ sortBy (getCompare $ contramap length $ Comparison compare) words
  -- 出力: ["Rust","Python","Haskell"]

Const と Contravariant

Const型もContravariantのインスタンスです。

newtype Const a b = Const { extract :: a }

instance Contravariant (Const a) where
  contramap _ (Const x) = Const x

Const a bは値 a を保持する 型です。入力変換がどのように行われても、保持している値は変化しません。

法则の証明

1. contramap id = id

contramap id (Const x) = Const x = id (Const x)

2. contramap f . contramap g = contramap (g . f)

(contramap f . contramap g) (Const x)
= contramap f (contramap g (Const x))
= contramap f (Const x)
= Const x
= contramap (g . f) (Const x)

positive位置とnegative位置

型構築子における位置は正負に分かれています。

a  -- positive位置
a -> Bool -- negative位置
(a -> Bool) -> Bool -- positive位置
((a -> Bool) -> Bool) -> Bool -- negative位置
a -> Bool -> Bool = a -> (Bool -> Bool) -- negative位置

関数の矢印 -> は右側をpositive、左側をnegativeとして扱います。Contravariantは、このnegative位置(入力側)に作用する変換を可能にします。

参考文献

Contravariantファンクタの詳細については、Hackageのドキュメントを参照してください。

タグ: Haskell functor contravariant fp 関数型プログラミング

5月30日 00:27 投稿