이 글은 책 Haskell Programming from First Principles 를 읽고 일부 내용을 정리한 것입니다.
fmap
:: (Functor f)
=> (a -> b) -> f a -> f b
Functor는 구조는 그대로 두고 안에 들어 있는 값만 바꾼다.
f a
에서 f
는 구조이고 a
는 값이다.
ghci> fmap (+1) [1, 2]
[2, 3]
ghci> fmap (+1) (Just 1)
Just 2
Natural Transformation은 Functor
와는 반대로 안에 들어 있는 값은 그대로 두고 구조만 바꾼다.
{-# LANGUAGE RankNTypes #-}
type Nat f g =
forall a . f a -> g a
값인 a
는 그대로 두고 구조인 f
만 g
로 바꾸는 Natural Transformation이다.
아래 코드에서 값은 그대로 두고 구조만 Maybe
에서 리스트로 바꾼다.
maybeToList :: Nat Maybe []
maybeToList Nothing = []
maybeToList (Just x) = [x]
RankNTypes
확장과 forall a .
은 생소한데 이것들 없이 Nat
를 생각해보자.
어떤 게 왜 있는지 잘 모를 때는 그게 없으면 어떤 문제가 있을지 생각해보면 된다.
아래처럼 적으면 =
오른쪽에는 a
가 있는데 왼쪽에는 없어서 a
가 뭔지 모르겠다고 에러가 난다.
type Nat f g =
f a -> g a
-- error:
-- Not in scope:
-- type variable `a`
아래처럼 =
왼쪽에도 a
를 적어주면 에러가 안 난다.
type Nat f g a =
f a -> g a
그런데 이렇게 하면 아래와 같이 구조가 아니라 값이 바뀌는 것을 방지할 수 없다.
degenerateMtl :: Nat Maybe [] Int
degenerateMtl Nothing = []
degenerateMtl (Just x) = [x + 1]
반면에 원래처럼 forall a .
을 이용하면 구조가 아니라 값이 바뀌는 것을 방지할 수 있다. 건드리고 싶어도 못 건드린다.
degenerateMtl' :: Nat Maybe []
degenerateMtl' Nothing = []
degenerateMtl' (Just x) = [x + 1]
-- error:
-- No instance for (Num a)
-- arising from a use of `+`
구조가 아닌 안에 있는 값 a
에 대한 정보가 없기 때문에 a
에 대해서 어떠한 연산도 시도할 수 없다.
디스코드 서버 하스켈 학교의 모든 분
forall을 이런 용도로 쓸수도 있네요. 좋은 내용 감사합니다.