(Это скорее комментарий, чем ответ, поскольку он не касается точного вопроса, заданного ОП.)
В дополнение к ответу Виллема: в настоящее время у нас есть другой способ заставить GHC принять этот класс.
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
Как GHC предлагает в своем сообщении об ошибке, мы можем включить AllowAmbiguousTypes
. ОП отметил, что тогда возникнут проблемы, если мы оценим что-то вроде g False
и есть два совпадающих экземпляра, например
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
Действительно, в таком случае g False
становится неоднозначным. Тогда у нас есть два варианта.
Во-первых, мы можем запретить использование обоих приведенных выше экземпляров, добавив функциональную зависимость b -> a
в класс (как предложил Виллем). Это делает g False
однозначным (и нам не нужно расширение в таком случае).
В качестве альтернативы, мы можем оставить оба экземпляра в коде и устранить неоднозначность вызова g False
, используя приложения типа (другое расширение). Например, g @() False
выбирает первый экземпляр, а g @((),()) False
выбирает второй.
Полный код:
{-# LANGUAGE MultiParamTypeClasses, FunctionalDependencies,
FlexibleInstances, AllowAmbiguousTypes, TypeApplications #-}
class Foo a b | a -> b where
f :: a -> Bool
g :: b -> Bool
h :: a -> b -> Bool
instance Foo () Bool where
f x = True
g y = y
h x y = False
instance Foo ((), ()) Bool where
f x = True
g y = not y
h x y = False
main :: IO ()
main = print (g @() False, g @((),()) False)