Typed Holes

Posted on September 8, 2020

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 :: Double

default 구문으로 Num 클래스의 디폴트값을 지정해 줄 수 있습니다. default 구문을 써주지 않으면

default (Integer, Double)

로 지정한 것으로 간주합니다.

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