{-# LANGUAGE MultiParamTypeClasses #-}
class SomeC a b where
method :: a -> b -> IO ()
instance SomeC Integer Double where
= putStrLn "Double ok"
method _ _
instance SomeC Integer Char where
= putStrLn "Char ok"
method _ _
> method 1 1.0
Double ok
> method 1 'a'
Char ok
타입 클래스를 정의할 때, 타입 매개 변수가 두 개 이상인 것도, 또는 하나도 없는 것도 가능하게 해주는 확장입니다.
확장을 끄고 위 클래스를 정의하면 에러가 납니다.
Too many parameters for class ‘SomeC’
• Enable MultiParamTypeClasses to allow multi-parameter classes)
(-- 클래스 매개 변수는 하나만 가능합니다.
Illegal instance declaration for ‘SomeC Integer Double’
• Only one type can be given in an instance head.
(Use MultiParamTypeClasses if you want to allow more, or zero.)
-- 인스턴스 헤드에는 하나의 타입만 있어야 합니다.
아마도 초창기 하스켈은, 인스턴스 헤드에 있는 타입의 가장 바깥을 싸고 있는 생성자하고만 매칭을 시도 했던 것 같습니다. 디폴트로 이 기능이 들어가 있지 않은 이유를 아는 분은 댓글 부탁드립니다.
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE FunctionalDependencies #-}
class SomeC a b c where
method :: a -> b -> c
instance SomeC Int Int Int where
= a + b
method a b
main :: IO ()
= putStrLn $ show $ method 1 2 main
위 코드는 method 의 결과 타입이 모호하다는 에러를 내뱉습니다. 왜 GHC가 추론하지 못할까요?
instance SomeC Int Int Double where
= (a + b) :: Double method a b
이런 인스턴스가 추가로 있다고 생각하면, method 1 2
를 보고, SomeC Int Int Int
를 골라야 할지, SomeC Int Int Double
을 골라야 할지 알 수 없습니다. 인스턴스가 하나만 있다고 GHC가 무조건 있는 인스턴스를 고르는 건 아닙니다.
이럴 때, 매개 변수로 들어오는 타입들 중 하나가 다른 타입에 의존한다고 알려주면 GHC가 추론을 마칠 수 있는 단서가 됩니다.
class SomeC a b c | a b -> c where
method :: a -> b -> c
instance SomeC Int Int Int where
= a + b method a b
| a b -> c
구문으로 c
타입은 a
, b
타입에 의존한다고 해 놓으면, a :: Int
, b :: Int
를 만나면 c :: Int
로 추론합니다.
물론 이 상태에서
instance SomeC Int Int Double where
= a - b method a b
을 추가하면
Functional dependencies conflict between instance declarations:
instance SomeC Int Int Int -- ...
instance SomeC Int Int Double -- ...
의존성 충돌이 난다고 에러가 납니다.