Неоднозначный тип с использованием явных квантификаторов

Минимальный пример кода:

class IntegralAsType a where
  value :: (Integral b) => a -> b

class (Num a, Fractional a, IntegralAsType q) => Zq q a | a -> q where
  changeBase:: forall p b . (Zq q a, Zq p b) => a -> b

newtype (IntegralAsType q, Integral a) => ZqBasic q a = ZqBasic a deriving (Eq)

zqBasic :: forall q a . (IntegralAsType q, Integral a) => a -> ZqBasic q a
zqBasic x = ZqBasic (mod x (value (undefined::q)))

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = fromIntegral (value (undefined::p)) -- ZqBasic is an instance of Num

Вот небольшая предыстория того, что я пытаюсь сделать: IntegralAsType обеспечивает безопасность типов во время компиляции, предотвращая что-то вроде сложения двух чисел с разными модулями. ZqBasic — это внутреннее представление типа Zq, есть и другие, поэтому Zq определяется именно так. Цель состоит в том, чтобы получить систему, прозрачную для внутреннего представления.

Моя проблема связана с функцией changeBase. Я использую явный forall для типа 'p', но все еще получаю "неоднозначную переменную типа a0 в ограничении (IntegralAsType a0), возникающую из-за использования значения"

Я смущен тем, почему я получаю эту ошибку. В частности, в предыдущем посте я получил помощь с чем-то вроде функции «zqBasic», которая, по-видимому, имеет те же настройки, что и функция changeBase. Я исправил ошибку неоднозначной переменной в zqBasic, добавив явный квантификатор 'forall q a .' Без этого квантификатора я получаю ошибку переменной неоднозначного типа. Я понимаю, зачем мне там нужен квантификатор, но я не понимаю, почему он не помогает для changeBase.

Спасибо


person crockeea    schedule 02.12.2011    source источник
comment
Дополнительные примечания: я использую расширения ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses и FunctionalDependencies. Приведенный выше код работает, когда (undefined::p) заменяется на (undefined::q) в 'changeBase'   -  person crockeea    schedule 02.12.2011


Ответы (3)


Использование ScopedTypeVariables здесь не помогает, потому что p, которое вы используете, в любом случае, похоже, не входит в область действия. Сравните следующие определения:

changeBase (ZqBasic x) = fromIntegral (value (undefined::foobar))

Это дает ту же ошибку, потому что также создает переменную нового типа.

changeBase (ZqBasic x) = fromIntegral (value (5::p))

Однако это дает другую ошибку. Соответствующие биты:

Could not deduce (Num p1) arising from the literal `5'
        (snip)
    or from (Zq q (ZqBasic q a), Zq p b)
      bound by the type signature for
                 changeBase :: (Zq q (ZqBasic q a), Zq p b) => ZqBasic q a -> b

Это показывает, что p создается как переменная нового типа. Я предполагаю, что forall в сигнатуре типа не включает в область действия (в фактических объявлениях) переменные типа, которые не являются параметрами типа класса. Однако он действительно делает переменную областью действия для объявления по умолчанию.

Во всяком случае, это ни здесь, ни там.

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

zq :: (Zq q a) => a -> q
zq _ = undefined

По сути, это просто дает вам прямой доступ на уровне терминов к функциональной зависимости, поскольку fundep делает q однозначным для любого конкретного a. Вы не можете получить фактическое значение, конечно, но это не главное. Если вас беспокоит undefined, используйте вместо этого [] :: [q] для аналогичного эффекта и используйте head или что-то еще, только когда вам нужно.

Теперь вы можете немного изменить ситуацию, используя предложение where или что-то еще, чтобы принудительно вывести правильные типы:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = b
    where b = fromIntegral (value (zq b))

Здесь происходит то, что b — это действительно то, что нам нужно, но нам нужно value, чтобы увидеть тип p, который определяется типом b, поэтому, назначив временное имя результату, мы можем использовать его для получения типа, который мы нужно.

Во многих случаях вы также можете сделать это без дополнительного определения, но с классами типов вам нужно избегать специального полиморфизма и гарантировать, что он не позволяет «рекурсии» включать другие экземпляры.

В связи с этим функция стандартной библиотеки asTypeOf предназначена именно для такого типа возни с типами.

person C. A. McCann    schedule 02.12.2011
comment
Просто для ясности: в приведенном выше коде есть две буквы «b»: первые две — это локальные переменные, а третье вхождение относится к типу из подписи, верно? - person crockeea; 02.12.2011
comment
@Eric: в последнем фрагменте кода все три использования b относятся к одному и тому же; он вообще не упоминает типы. На самом деле цель состоит в том, чтобы избежать необходимости ссылаться на типы. - person C. A. McCann; 02.12.2011

Вызов value (undefined::p) преобразует p в некоторый тип a0. Из типа value единственное, что мы можем понять, это то, что a0 является целочисленным типом.

Это значение передается в fromIntegral, который преобразует a0 в b. Из типа fromIntegral единственное, что мы можем понять, это то, что a0 является целочисленным типом.

Ничто не определяет тип a0, поэтому для разрешения неоднозначности необходима аннотация типа в выражении value (undefined::p). Глядя на ваши сигнатуры типов, похоже, что value должен иметь возможность генерировать правильный тип возвращаемого значения без каких-либо дополнительных преобразований. Можете ли вы просто удалить вызов fromIntegral?

Изменить

Вам необходимо включить расширение ScopedTypeVariables. Без ScopedTypeVariables переменная типа не может упоминаться более чем в одной сигнатуре типа. В этом случае имя переменной p не ссылается на одну и ту же переменную в сигнатуре типа функции и в ее теле.

У меня работает следующий код:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase = zqBasicChangeBase

zqBasicChangeBase :: forall p b q a.
  (IntegralAsType q, IntegralAsType p, Integral a, Zq p b) => ZqBasic q a -> b
zqBasicChangeBase (ZqBasic x) = fromIntegral (value (undefined :: p) :: Integer)
person Heatsink    schedule 02.12.2011
comment
Спасибо за понятное объяснение, но это не работает. Разве fromIntegral не должен работать с любым (произвольным) целочисленным значением и преобразовывать его в правильный тип? Думаю, исходя из контекста типа возвращаемого значения, следует сделать вывод о правильном типе вызова fromIntegral. Кроме того, что бы это ни стоило, код работает нормально, если я использую (value (undefined::q)) вместо undefined::p, поэтому проблема заключается в том, что p не связан аргументом? - person crockeea; 02.12.2011
comment
Я также попытался явно указать тип вызова значения, т.е. ((значение (undefined::p))::Integer), но получил ту же ошибку, поэтому проблема, похоже, не связана с неоднозначным типом из стоимость. - person crockeea; 02.12.2011
comment
Вам нужно расширение ScopedTypeVariables для сигнатур типа, чтобы оно означало то, что вы хотите. Он компилируется для меня с включенным расширением. - person Heatsink; 02.12.2011
comment
Очень интересно, потому что я уже использую ScopedTypeVariables. Я использую GHCi 7.0.3 - person crockeea; 02.12.2011
comment
Самое первое, что я сделал, пытаясь скомпилировать ваш код, — это вынес определение changeBase в отдельную функцию и написал ее сигнатуру типа. Переменные типа, представленные в определении класса, не входят в область действия экземпляров класса. - person Heatsink; 02.12.2011
comment
Мне кажется, что переменные типа, представленные в определении класса в моем примере, относятся только к «a» и «q», а не к «b» и «p», или это относится к переменным типа в сигнатуре типа любой функции в класс? В любом случае, весь смысл этого примера в том, чтобы позволить изменять базы между различными типами Zq. Я не хочу иметь отдельную функцию для каждого типа ZqInstance, поэтому я использую класс! Как-нибудь обойти это? - person crockeea; 02.12.2011
comment
Переменные типа, появляющиеся где-либо в определении класса, ограничены определением класса (или некоторым подвыражением определения класса). Это относится к a, q, b и p. Помещение кода в новую функцию не ограничивает возможности использования класса, оно просто перемещает код за пределы определения класса и вносит некоторый шум. Во всяком случае, у camccann, кажется, есть более приятное решение. - person Heatsink; 02.12.2011

Два (по существу эквивалентных?) подхода, которые работают:

1: Использование К.А. Подход Макканна:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = b
    where b = fromIntegral ((value (zq b))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope

2: Вместо подписи a->b я изменил подпись changeBase на b->a Затем работает следующее:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase x = fromIntegral ((value (zq x))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope

Цель всегда состояла в том, чтобы иметь доступ к параметрам типа как для аргумента, так и для типа возвращаемого значения, и оба этих подхода позволяют мне это сделать.

Кроме того, ответ на мой вопрос о разнице между конструктором «zqBasic» и «changeBase», как указал C.A.McAnn, заключается в том, что «p» не помещается в область видимости в объявлениях, даже с явным forall. Если кто-нибудь может объяснить, почему это так, буду признателен.

person crockeea    schedule 02.12.2011