GHC User Guide - 7.14. Typed Holes
타입 체커와 소통하기 위한 기능입니다. 타입 체커가 폴리모픽 타입을 어떤 타입으로 추론하는지 궁금할 때, 궁금한 자리에 _ 를 써주면 에러 메시지에 추론 결과를 보여줍니다. _x, _y 이렇게 구별되게 홀 표시를 하기도 하지만, 에러 메시지를 읽을 때 위치를 알려주는 역할 정도만 하지 실제 identifier로 쓰이진 않습니다. 아래 코드를 보면 첫 번째 _x와 두 번째 _x는 다른 홀입니다.
> code = _x : _x
<interactive>:7:8: error:
• Found hole: _x :: a
Where: ‘a’ is a rigid type variable bound by
the inferred type of code :: [a]
...
<interactive>:7:13: error:
• Found hole: _x :: [a]
Where: ‘a’ is a rigid type variable bound by
the inferred type of code :: [a]
...
> code = _ : _
-- 결과는 위와 같습니다.code는 타입 서명을 적어주지 않았지만, (:)의 결과값이 곧 타입이니,
code :: [a]으로 추론합니다.
(:) 함수가 인자 두 개를 받는데, 첫 번째는 a로 두 번째는 [a]로 추론하는 걸 볼 수 있습니다. 결과가 a, [a]등으로 폴리모픽한 모습이어서 전혀 추론하지 않은 것처럼 오해할 수 있습니다. 타입 추론은 항상 구체 타입까지 추론하는게 아닙니다. 추론 결과가 모든 타입인 경우도 있습니다
여기선 모든(forall a.a라 표현합니다.) 타입으로 추론한 상태입니다. 모든 타입을 다 처리할 수 있다는 의미입니다. 아무 타입 중 하나로 추론하는게 아니라 모든 타입입니다.
참고 - forall
여기서는 (:)의 결과로 [a] 타입이라 추론했고, 첫 번째 인자는 이 리스트 속에 들어있는 a와 같아야만 한다고 추론한 겁니다. 예를 들어 첫 번째 인자가 Int고, 두 번째 인자가 [Char] 이런식이면 안된다는 정보까진 추론한 겁니다. 모든 타입으로 추론했고, 두 인자간의 관계까지만 추론한 상태라고 볼 수 있습니다.
> import Control.Monad.Reader
> func x = runReader x 1
> :t func
func :: Num r => Reader r a -> a --- (1)
> func x = runReader _ 1
• Found hole: _ :: Reader Integer a --- (2)
Where: ‘a’ is a rigid type variable bound by
the inferred type of func :: p -> a
...
> :t runReader
runReader :: Reader r a -> r -> a(1)은 Num 클래스까지만 추론됐고, (2)는 Integer까지 추론됐습니다. monomorphismRestriction 확장을 끄거나 켜도 결과는 같습니다.
ghc에서 몇 몇 클래스들은, 클래스까지만 추론된 후 더 이상 추론 단서가 없다면 디폴트 타입으로 추론합니다. 홀은 runReader 함수가 두 번째 인자로 1을 받는 걸 보고 Num r => Reader r a 로 추론을 끝내지 않고 Reader Integer a까지 갔습니다.
컴파일 할 때와, GHCi에서 타입 체커 동작이 다릅니다. GHCi는 타입 디폴트 규칙을 numeric에만 한정하지 않고, Show, Eq, Ord 클래스까지 확장합니다. 일정한 동작을 보이면 좋겠는데, 때에 따라 다르다 하니 좀 까다롭습니다.
어떤 때에 디폴트 추론이 일어나는지 좀 더 명확해지면 내용을 추가하도록 하겠습니다.
디폴트 타입 정보를 보는 명령어 :type +d
> :type +d 1
1 :: Integer
> :type +d 1.0
1.0 :: Doubledefault 구문으로 Num 클래스의 디폴트값을 지정해 줄 수 있습니다. default 구문을 써주지 않으면
default (Integer, Double)로 지정한 것으로 간주합니다.