소문자로 쓰여 있는 건, 무조건 나중에 “필요한 만큼” 구체 타입으로 추론됩니다.
import Control.Monad.Reader
import Data.Function
func1 :: m ()
= do
func1 <- ask
cfg return ()
아래와 같은 에러가 납니다.
No instance for (Monad m) arising from a do statement
• Possible fix:
Monad m) to the context of
add (type signature for:
the func1 :: forall (m :: * -> *). m ()
...
8 | cfg <- ask
Ambiguous type variable ‘a0’ arising from a use of ‘ask’
• MonadReader a0 m)’ from being solved.
prevents the constraint ‘(Relevant bindings include
func1 :: m ()
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instances exist:
instance [safe] Monad m => MonadReader r (ReaderT r m)
-- Defined in ‘Control.Monad.Reader.Class’
...plus 13 instances involving out-of-scope types
-fprint-potential-instances to see them all)
(use ...
8 | cfg <- ask
문장 그대로 읽으면 do구문을 보고 instance (Monad m)
을 찾아야 하는데, 그런 인스턴스가 없다는 얘기입니다.
func1
이 do
를 쓰니 Monad
입니다. m
은 모든 타입이니 당연히 Monad m
인스턴스란 건 없습니다. 왜 Monad m
을 could not deduce
라고 하지 않고, No instance
에러라고 했을까요? 여기선 m
을 deduce
하지 못한게 아닙니다. 아무 제약constraint없이 m
이라고만 쓰면 forall m. m
이란 뜻으로 모든 타입을 다 받을 수 있다고 결정된겁니다. 이렇게 찾은 m
의 Monad
인스턴스는 없습니다. 그런데 만일 여기에 Constraint가 있다면, Constraint를 이용해서 타입 추론deduce을 시도 합니다. 보통 Constraint가 있어 deduce시도를 해봤는데도 필요한 타입을 찾지 못하면 could not deduce 에러가 납니다. 아래와 같이 Constraint를 일부 추가하고
func1 :: (Monad m) => m ()
다시 컴파일 하면,
Could not deduce (MonadReader a0 m) arising from a use of ‘ask’
• : Monad m
from the contexttype signature for:
bound by the func1 :: forall (m :: * -> *). Monad m => m ()
The type variable ‘a0’ is ambiguous
Relevant bindings include
func1 :: m () (bound at ...)
These potential instances exist:
instance [safe] Monad m => MonadReader r (ReaderT r m)
-- Defined in ‘Control.Monad.Reader.Class’
...plus 13 instances involving out-of-scope types
-fprint-potential-instances to see them all)
(use ...
8 | cfg <- ask
ask
를 쓰는 걸로 봐서 m
은 MonadReader a m
인스턴스일텐데, 서명의 Constraint에는 Monad m
만 있을 뿐, 더 추론할 정보가 없습니다.
func1 :: (Monad m, MonadReader c m) => m ()
이렇게 하면 컴파일 성공입니다. 함수 내부에서 특정 타입을 쓰도록 바꾸고 타입 추론을 더 살펴 보겠습니다.
func1 :: (Monad m, MonadReader c m) => m a
= do
func1 <- ask
cfg return $ cfg + 1
Couldn't match type ‘c’ with ‘a’
• :
arising from a functional dependency between constraintsMonadReader a m’
‘of ‘ask’
arising from a use MonadReader c m’
‘type signature for:
arising from the func1 :: forall (m :: * -> *) c a. (Monad m, MonadReader c m) => m a
type variable bound by
‘c’ is a rigid type signature for:
the func1 :: forall (m :: * -> *) c a. (Monad m, MonadReader c m) => m a
type variable bound by
‘a’ is a rigid type signature for:
the func1 :: forall (m :: * -> *) c a. (Monad m, MonadReader c m) => m a
8 | cfg <- ask
a
와 c
는 rigid type입니다. 참고 - rigid type variable
forall c, 즉 모든 타입의 a와 c로 정해졌습니다.1 서명에 a와 c는 어떤 제약도 없으므로 어떤 타입이든 들어 올 수 있습니다. 함수 내부에서도 어떤 타입이 들어와도 처리할 수 있어야 합니다.
class Monad m => MonadReader r m | m -> r where
ask :: m r
ask
는 m r
타입입니다. ask
의 결과값은 func1
함수의 결과값과 타입이 같을테니 m a
일 겁니다. 함수내에서는 r
과 a
가 같은 값으로 처리되고 있지만, 서명에는 둘 이 같다는 정보가 없습니다. MonadReader
서명을 보면 m -> r
즉 m
이 정해지면 r
은 따라서 고정되는 functional dependency가 있습니다. r
은 m
이 바뀌지 않으면 고정입니다. 무슨 말이냐 하면, m
이 IO
로 추론되고 r
이 한 번 Int
로 추론 됐다면, MonadReader Int IO
로 추론되고, 한 컨텍스트 안에서 MonadReader Bool IO
같은 타입으로 추론할 수 없습니다. 에러를 그대로 번역하면
MonadReader r m
에서 r
은 타입 서명에 있는 제약에서 c
로 한 번 추론됐으니, 결과 타입때문에 추론된 MonadReader a m
의 a
와 매칭 할 수 없다는 얘기입니다.
그럼 GHC의 추론에 의존하지 않고, r
을 Int
로 고정해 봅시다.
func1 :: (Monad m, MonadReader Int m) => m Int
Non type-variable argument in the constraint: MonadReader Int m
• Use FlexibleContexts to permit this)
(In the type signature:
• func1 :: (Monad m, MonadReader Int m) => m Int
6 | func1 :: (Monad m, MonadReader Int m) => m Int
제약constraint에는 구체 타입을 적어 줄 수 없습니다. 구체 타입 지정을 위해 언어 확장을 켭니다. 참고 FlexibleContexts
{-# LANGUAGE FlexibleContexts #-}
타입 서명과 함수 내부에서 쓰는 타입이 일치해야 합니다.
이러면 Couldn’t match type 에러가 납니다. 1번은 별 어려움이 없는 말이지만, 2번은 가끔 혼동을 줄 때가 있습니다. 어려운 명제로 얘기하지 않고, 코드 실행 입장으로 생각하면 쉽게 이해가 갑니다. 서명에는 아무 타입, 내부에서는 MonadReader 인스턴스만 가능하다고 하면, GHC가 서명만 보고 코드 조립을 성사(컴파일)시켜 놓으면, 나중에 문제가 생길거라 추측할 수 있습니다.
arising from
No instance for (Monad m) arising from a do statement .. Ambiguous type variable a0 arising from a use of 'ask'
이렇게 deduced from 을 쓰지 않고 arising
을 쓰는 이유는 deduced 해서 나온 정보일 수도 있고, specified 해서 나온 정보일 수도 있기 때문인 것 같습니다. 번역하자면 do 구문에서 나온 (Monad m) 인스턴스가 없어서
, ask에서 나온 타입 변수 a0가 뭔지 몰라서
입니다.↩︎