rigid 단어 뜻 : 엄격한, 융통성이 없는
rigid 단어를 보면 타입이, 다른 타입으로 추론할 수 없게 어느 하나로 고정됐다는 의미처럼 보입니다. 고정된 건 맞는데, 어느 타입 하나로 고정된 게 아닙니다. “여러 타입이 될 수도 있는 성격”으로 함수 서명에서 고정됐다는 의미입니다. 다른 말로 사용자가 지정한 타입(user specified type - 추론 가능한 열린 타입이 아닌, 닫힌 타입으로 사용자가 지정했다라는 의미 같습니다.)이라 말 하기도 합니다.
f :: a -> Bool
= a == "x" f a
a
는 아무 타입이나 된다고 했으니, String
타입으로 추론하면 아무 문제가 없을 것 같지만 에러가 납니다. (보통 폴리모픽 변수를 쓰면 forall
이 생략되었다고 보면 됩니다.)
Couldn't match expected type ‘a’ with actual type ‘[Char]’
• type variable bound by
‘a’ is a rigid type signature for:
the f :: forall a. a -> Bool
...
a
는 타입이 정해지지도 않았는데, [Char]
와 매치할 수 없다는 에러입니다.
“a
는 아무타입이나 될 수 있으니 추론하면 되는 거 아니었어?”
forall a
를 오해해서 생기는 혼란입니다. a
는 정해지지 않은게 아니라, “모든 타입”으로 정해졌습니다. a
는 모든 타입을 받는다고 해 놓고, 함수 내부에서는 "x"
와 같은 String
타입이어야 한다고 해놨으니 GHC가 매치가 안된다고 불평합니다.
폴리모픽 타입 변수 forall a. a
의 해석이 가끔 헷갈립니다.
2번으로 해석하면 위와 같은 오류가 안나고 a
를 String
으로 추론해줘야 할 것 처럼 보입니다. 하지만 선언에서 모든 타입이라고 선언해 놨으니 함수 내부에서 String
이라는 단일 타입으로 고정하면 안됩니다. 1번으로 해석해야 혼란이 덜 생깁니다.
정리하면, 위 함수는 선언은 모든 타입, 함수 내부에서는 String
단일 타입으로 쓰고 있기 때문에, 선언과 정의가 맞지 않아 에러가 납니다.
Eq a => a -> [a] -> Bool
elem _ [] = False
elem x (y:ys) = x == y || elem x ys
함수 서명에서 a
는 Eq a
인스턴스가 있는 타입으로 한정됐습니다.
함수 내부에서도 ==
을 만나면 a
는 Eq a
인스턴스가 있는 타입으로 한정됩니다.
함수 서명과 함수 내부가 매치됩니다. 타입 추론 작업이 타입 서명을 벗어나지 않습니다.
함수에서 constraint의 의미는, constraint를 추론 단서로 쓰는게 아니라,
“함수 body에서 (==)
을 쓰는 걸 봐서, Eq
클래스의 인스턴스만 받는다고 constraint를 이용해 외부에 알려줘” 입니다.
length :: [a] -> Int
length [] = 0
length (_:xs) = 1 + length xs
많은 문서에서 예시로 나오는데, elem
과 마찬가지로 length
경우도 a
의 구체 타입은 알 필요가 없습니다.
타입 추론은 항상 구체 타입이 결정될 때까지 하는 게 아닙니다. 소속 타입 클래스까지만 추론해서 작업을 할 수 있으면 클래스까지만 추론합니다. elem
에서는 (==)
메소드를 쓰니 Eq
클래스의 인스턴스까지만 확인합니다.
head :: [a] -> a
head (x:_) = x
이 함수만의 작업에선 a
가 무슨 타입인지 알 필요가 없습니다. 이 함수가 다른 코드와 조합할 때 a
의 구체 타입이 필요해지면 그 때 가서 더 추론하면 됩니다.
> :t head
head :: [a] -> a -- a는 아직 추론될 필요가 없습니다.
> :t ("p"++).head
"p"++).head :: [[Char]] -> [Char] -- ("p"++) 함수 때문에 Char로 추론되었습니다. (