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のドキュメントを参照してください。