contravariant functors reverse the direction of composition. - Wikipedia
covariant functor
F(f∘g)=F(f)∘F(g)
contravariant functor
F(f∘g)=F(g)∘F(f)
covariant map with ((->) r)
(a -> b) -> (f a -> f b)
(a -> b) -> ((->) r a) -> ((->) r b))
(a -> b) -> (r -> a) -> (r -> b)
contravariant map with (_ -> c)
(b -> a) -> (f a -> f b)
(b -> a) -> (a -> c) -> (b -> c)
(a0 -> a1) 함수가 있을때 합성을 통해 함수의 출력을 변경하는 함수를 만들 수 있다.
(.) :: (a1 -> z) -> (a0 -> a1) -> (a0 -> z)
((->) a0)를 functor로 본다면 (a1 -> z) 함수에 ((->) a0)의 fmap을 적용해서 lifting 하는것과 함수의 합성은 동일하다.
fmap :: (a1 -> z) -> (f a1) -> (f z)
그리고 합성의 방향을 바꾼다면 a0가 아닌 타입의 값을 a0로 변환하여 이 함수를 실행시킬 수 있는 함수를 만들 수 있다.
flippedCompose :: (r -> a0) -> (a0 -> a1) -> (r -> a1)
(_ -> a1)을 functor로 만들어보면 fmap으로는 이걸 할 수 없다. fmap에 (_ -> a1)을 적용해보면 이렇게 되기 때문이다.
fmap :: (r -> a0) -> (r -> a1) -> (a0 -> a1)
이때 필요한게 contravariant functor 이다. fmap과 비교를 위해 나란히 써봤다.
fmap :: (a -> b) -> f a -> f b
contraMap :: (a -> b) -> f a <- f b
contraMap :: (a -> b) -> f b -> f a
(a0 -> a1) 함수를 중심으로 생각을 해보면 compose/fmap은 함수 실행 후 출력을 변경하는 함수를 지정할 수 있는 반면, pipe/contraMap은 함수가 실행되기 전 입력값을 a0 타입으로 조정하는데 사용된다.
함수 타입에서 반환 타입은 공변적(covariant) 이고, 인자 타입은 반공변적(contravariant) 이라는것을 생각하면 자연스럽게 이해가 될 것이다.
하스켈에서는
instance Contravariant (_ -> z) where
...
위와 같이 사용할 수는 없기 때문에 Op, Predicate와 같이 Contravariant를 구현하고 있는 특화된 타입이 존재한다.
https://math.stackexchange.com/questions/702551/variance-of-functor
https://edykim.com/ko/post/what-is-coercion-and-anticommunism/