{-# LANGUAGE MultiParamTypeClasses #-}
class SomeC a b where
method :: a -> b -> IO ()
instance SomeC Integer Double where
method _ _ = putStrLn "Double ok"
instance SomeC Integer Char where
method _ _ = putStrLn "Char ok"
> 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
method a b = a + b
main :: IO ()
main = putStrLn $ show $ method 1 2 위 코드는 method 의 결과 타입이 모호하다는 에러를 내뱉습니다. 왜 GHC가 추론하지 못할까요?
instance SomeC Int Int Double where
method a b = (a + b) :: Double이런 인스턴스가 추가로 있다고 생각하면, 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
method a b = a + b| a b -> c 구문으로 c 타입은 a, b 타입에 의존한다고 해 놓으면, a :: Int, b :: Int를 만나면 c :: Int로 추론합니다.
물론 이 상태에서
instance SomeC Int Int Double where
method a b = a - b을 추가하면
Functional dependencies conflict between instance declarations:
instance SomeC Int Int Int -- ...
instance SomeC Int Int Double -- ...의존성 충돌이 난다고 에러가 납니다.