Скажем, у меня есть два класса, подобных этому:
{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts #-}
class Foo a b
class Bar a where
foo :: Foo a b => a
Примечательно, что b
из foo
не может быть выведено ни из использования foo
, ни из головы экземпляра.Теперь давайте попробуем реализовать экземпляр для этого:
data A = A
-- This implementation doesn't actually need the Foo A b constraint, but for the sake of the question it's required here.
a :: Foo A b => A
a = A
Пока все хорошо.a
должен стать foo
в нашем случае.У него даже есть правильный тип, верно?Итак, давайте продолжим и реализуем экземпляр:
instance Bar A where
foo = a
К сожалению, этот экземпляр не компилируется и вместо этого выдает эту ошибку:
instance-ambiguous.hs:15:9: error: …
• Could not deduce (Foo A b0) arising from a use of ‘a’
from the context: Foo A b
bound by the type signature for:
foo :: forall b. Foo A b => A
at /home/sven/instance-ambiguous.hs:15:3-5
The type variable ‘b0’ is ambiguous
• In the expression: a
In an equation for ‘foo’: foo = a
In the instance declaration for ‘Bar A’
|
Compilation failed.
Сначала это сообщение об ошибке кажется совершенно бессмысленным, потому чтопохоже, что GHC может просто объединить b
и b0
и сделать вывод совершенно идеального типа.Потом я вспомнил, что b
и b0
не видны для типа foo
или a
, и GHC не может их объединить, потому что в нем нет ничего, что гарантировало бы, что b
и b0
действительновсегда одно и то же, и это не такая неожиданная ошибка при работе с неоднозначными типами.
Обычно, когда я сталкиваюсь с такими ошибками, я могу решить их, используя TypeApplications и ScopedTypeVariables, например:
{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts, TypeApplications, ScopedTypeVariables #-}
class Foo a b
class Bar b
foo :: forall b a. (Bar b, Foo a b) => a
foo = undefined
bar :: forall b a. (Bar b, Foo a b) => a
bar = foo @b
Здесь я могу явно указать @b
, поскольку сигнатура типа bar
принесла его в область видимости.Поэтому я попытался сделать то же самое с моим экземпляром (используя InstanceSigs):
instance Bar A where
foo :: forall b. Foo A b => A
foo = a @b
Это тоже не компилируется и выдает эту ошибку:
instance-ambiguous.hs:16:10-31: error: …
• Could not deduce (Foo A b0)
from the context: Foo A b
bound by the type signature for:
foo :: forall b. Foo A b => A
at /home/sven/instance-ambiguous.hs:16:10-31
The type variable ‘b0’ is ambiguous
• When checking that instance signature for ‘foo’
is more general than its signature in the class
Instance sig: forall b. Foo A b => A
Class sig: forall b. Foo A b => A
In the instance declaration for ‘Bar A’
|
Compilation failed.
Я не уверен,но я думаю, это означает, что GHC считает, что мой Foo A b => A
в данном экземпляре относится к некоторому другому b
, чем тот, который указан в объявлении класса.
Использование объявления шаблона в foo
для получения оригинала b
в области видимости также не работает, потому что привязки к шаблону запрещены в объявлениях экземпляров.
Теперь вопрос: каковы мои варианты решения этой проблемы?
Я знаю, что могупросто используйте Proxy
(s / y / ie /) s везде и не слышите ни одной из этих проблем неоднозначности, но я обычно нахожу TypeApplications более элегантными, чем Proxy
s, и хотел бы использовать их здесь, особенно потому, что затронутые классы являются частьюмоего открытого API.
Я мог бы также включить b
в качестве переменной класса Bar
, но я думаю, что это изменило бы значение Bar
на то, что я не хочу, потому чтозатем модулиtances могут выбирать, для каких b
s создавать экземпляры, но я хочу, чтобы каждый Bar a => a
работал для каждого b
, для которого существует Foo a b
.