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
type of code :: [a]
the inferred ...
<interactive>:7:13: error:
Found hole: _x :: [a]
• Where: ‘a’ is a rigid type variable bound by
type of code :: [a]
the inferred ...
> 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
type of func :: p -> a
the inferred ...
> :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 :: Double
default
구문으로 Num
클래스의 디폴트값을 지정해 줄 수 있습니다. default
구문을 써주지 않으면
Integer, Double) default (
로 지정한 것으로 간주합니다.