폴리모픽 타입 변수 a는 정말 어떤 타입이든 상관 없는걸까?

Posted on July 11, 2020

a 일 때는 어떤 타입도 가능

func :: a -> IO ()
func _ = putStrLn "it is OK"

> func 1
it is OK

> func (Just 1)
it is OK

> func (+)
it is OK 

> func id
it is OK 

하스켈이 a -> IO () 를 만났을 때, a는 어떤 타입도 올 수 있습니다. * 카인드든, * -> * 카인드든 어떤 타입도 받아 들입니다.
그럼 f a, m a 또는 a b 등 임의의 소문자 두 개를 연속으로 쓴 경우는 어떨까요?

f a 일 때 f는 forall k -> * 카인드만 가능

func :: a b -> IO ()
func x = putStrLn "it is OK"

ghci> func 1
<interactive>:75:6: error:
No instance for (Num (a0 b0)) arising from the literal ‘1
In the first argument of ‘func’, namely ‘1
      In the expression: func 1
      In an equation for ‘it’: it = func 1

위 오류를 해석해 보면, a b -> IO () 를 만나면, a* 카인드는 올 수 없습니다. 뒤에 b라는 ’인자’를 받는 모양이기 때문에 a는 적어도 * -> * 카인드는 되어야할 것처럼 보입니다. 하지만 b* -> *가 될지, 어떤 카인드가 될지 알 수 없으니 정확히 말하면, forall {k} k -> *가 되어야 합니다. 그래야 b가 뒤따라오는게 말이 됩니다. 게다가 b를 받아야 하니, 첫번째 인자의 카인드는 b와는 같은 카인드여야 합니다. (공백을 인자 적용이라는 함수로 생각하는 건 어떨까요)

ghci> :t func
func :: forall {k} (a :: k -> *) (b :: k). a b -> IO ()

런타임은 인자 1을 보고, a b1과 맞추려고 할 것입니다. 오류 메시지는

Num a => ... (aNum 클래스의 인스턴스이다.란 뜻)  Num은 타입이 아닙니다. 클래스는 별도로 Constraint 카인드로 분류합니다.
=> 의 왼쪽은 Constraint 만 올 수 있습니다.

> :k Num
Num :: * -> Constraint
-- Num 은 * 카인드를 받아 Constraint 를 만든다.

:k 카인드는 타입 생성자(값 생성자 아니고)와 클래스만 대상으로 합니다.
리터럴 1의 타입은 뭘까요?

> :t 1
1 :: Num p => p

리터럴 1만 보고 타입까지 결정되진 않습니다. 클래스까지만 결정 되면, 클래스에 있는 메소드는 쓸 수 있지만, 최종 런타임 코드를 만들어내려면 메소드의 구현체가 결정되야 합니다. 이 구현체는 어떤 코드들과 어울리냐에 따라 나중에 결정됩니다.

Github 계정이 없는 분은 메일로 보내주세요. lionhairdino at gmail.com