Реализация методов экземпляра с переменными неоднозначного типа, не содержащимися в заголовке класса - PullRequest
0 голосов
/ 01 февраля 2019

Скажем, у меня есть два класса, подобных этому:

{-# 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.

1 Ответ

0 голосов
/ 01 февраля 2019

Кажется, что нет способа разрешить неоднозначность для экземпляров, поэтому Proxy или Tagged кажется неизбежным для определения класса, но вы можете заключить его в , используя классс TypeApplications.

class Bar a where
  foo_ :: Foo a b => proxy b -> a

foo :: forall b a. (Bar a, Foo a b) => a
foo = foo_ (Proxy @b)
...