MultiParamTypeClasses - Почему эта переменная типа неоднозначна? - PullRequest
2 голосов
/ 07 июня 2019

Скажем, я определяю многопараметрический класс типов следующим образом:

{-# LANGUAGE MultiParamTypeClasses, AllowAmbiguousTypes, FlexibleContexts, FlexibleInstances #-}

class Table a b c where
  decrement :: a -> a
  evalutate :: a -> b -> c

Затем я хочу определить функцию, которая для простоты использует decrement:

d = decrement

Когда я пытаюсь загрузить это в ghci (версия 8.6.3):

• Could not deduce (Table a b0 c0)
    arising from a use of ‘decrement’
  from the context: Table a b c
    bound by the type signature for:
               d :: forall a b c. Table a b c => a -> a
    at Thing.hs:13:1-28
  The type variables ‘b0’, ‘c0’ are ambiguous
  Relevant bindings include d :: a -> a (bound at Thing.hs:14:1)
  These potential instance exist:
    instance Table (DummyTable a b) a b

Это сбивает с толку меня, потому что тип d в точности совпадает с типом decrement, который обозначается в объявлении класса.

Я подумал о следующем обходном пути:

data Table a b = Table (a -> b) ((Table a b) -> (Table a b))

Но это кажется неудобным с точки зрения обозначений, и я также просто хотел узнать, почему я вообще получил это сообщение об ошибке.

1 Ответ

5 голосов
/ 07 июня 2019

Проблема в том, что, поскольку decrement требует только тип a, невозможно определить, какие типы b и c должны быть, даже в точке, где вызывается функция (таким образом, преобразование полиморфизма в определенный тип), поэтому GHC не сможет решить, какой экземпляр использовать.

Например: предположим, у вас есть два экземпляра таблицы: Table Int String Bool и таблица Int Bool Float; Вы вызываете свою функцию d в контексте, где она должна отображать Int на другую Int - проблема в том, что соответствует экземплярам! (a является Int для обоих).

Обратите внимание, как, если вы сделаете свою функцию равной evalutate:

d = evalutate

тогда компилятор принимает это. Это связано с тем, что, поскольку evalutate зависит от трех параметров типа a, b и c, контекст на сайте вызова позволит не неоднозначно разрешать экземпляры - просто проверьте, какие типы для a, b и c находятся в место, где это называется.

Это, конечно, обычно не проблема для классов с одним параметром - нужно решить только один тип; когда мы имеем дело с несколькими параметрами, все усложняется ...

Одним из распространенных решений является использование функциональных зависимостей - сделать b и c зависимыми от a:

 class Table a b c | a -> b c where
  decrement :: a -> a
  evalutate :: a -> b -> c

Это говорит компилятору, что для каждого экземпляра Table для данного типа a будет один и только один экземпляр (b и c будут однозначно определены a); поэтому он будет знать, что не будет никаких двусмысленностей, и с радостью примет ваше d = decrement.

...