Аннотирование переменной неоднозначного типа для многопараметрических классов

#haskell

#haskell

Вопрос:

Я работаю с классом типа, в котором я могу «измерить» некоторое свойство типа v для объекта типа a . Например, рассмотрим определения, приведенные ниже:

 {-# LANGUAGE MultiParamTypeClasses #-}

data SizedElem a =
  SizedElem
    { getSize :: Size
    , getElem :: Elem a
    }

newtype Size =
  Size Integer

newtype Elem a =
  Elem a

class Measured a v where
  measureTypeClass :: a -> v

instance Measured (Elem a) (SizedElem a) where
  measureTypeClass (Elem a) = SizedElem (Size 1) (Elem a)
  

Попытка объединить функции для измерения элемента и последующего извлечения размера следующим образом

 broken :: Elem a -> Size
broken = getSize . measureTypeClass
  

приводит к жалобе от GHC:

 Ambiguous type variable ‘a0’ arising from a use of ‘measureTypeClass’
prevents the constraint ‘(Measured
                            (Elem a) (SizedElem a0))’ from being solved.
...
Probable fix: use a type annotation to specify what ‘a0’ should be.
These potential instance exist:
  instance Measured (Elem a) (SizedElem a)
  

Наивно, я просто попытался аннотировать тип, который я ожидаю в середине:

 broken' :: Elem a -> Size
broken' xs =
  let meas = measureTypeClass xs :: SizedElem a
   in getSize meas
  

Однако это приводит к несколько иной ошибке:

 No instance for (Measured (Elem a) (SizedElem a2))
  arising from a use of ‘measureTypeClass’
In the expression: measureTypeClass xs :: SizedElem a
In an equation for ‘meas’:
    meas = measureTypeClass xs :: SizedElem a
  

Если я правильно понимаю, это означает, что a интерпретируемый из аннотации :: SizedElem a тип отличается от того, который указан в сигнатуре типа. Как я могу сообщить компилятору, что это должна быть переменная того же типа? Простое применение {-# LANGUAGE ScopedTypeVariables #-} не решило проблему.

Кроме того, все работает нормально для определенного типа, например

 specificWorks :: Elem Char -> Size
specificWorks xs =
  let meas = measureTypeClass xs :: SizedElem Char
   in getSize meas
  

или, если сигнатура типа имеет переменную одного и того же типа как для ввода, так и для вывода

 elemWorks :: Elem a -> Elem a
elemWorks = getElem . measureTypeClass
  

Кроме того, также работает полный отказ от класса type, но для моего реального приложения мне нужен класс type по другим причинам:

 measureDirect :: Elem a -> SizedElem a
measureDirect (Elem a) = SizedElem (Size 1) (Elem a)

directWorks :: Elem a -> Size
directWorks = getSize . measureDirect
  

Ответ №1:

Чтобы использовать расширение ScopedTypeVariables , добавьте forall квантификатор к сигнатуре функции:

 getSize' :: forall a. Elem a -> Size
getSize' xs =
  let meas = measureTypeClass xs :: SizedElem a
  in getSize meas
  

Другой метод, который применим здесь, — это «трюк с ограничением». Эмпирическое правило состоит в том, чтобы никогда не иметь заголовка экземпляра (справа от => ), где переменная встречается дважды, и вместо этого использовать ограничение равенства.

 instance (a ~ b) => Measured (Elem a) (SizedElem b) where
  measureTypeClass (Elem a) = SizedElem (Size 1) (Elem a)
  

Комментарии:

1. Это решило проблему! Спасибо, что также указали на трюк с ограничением. Я не знал об этом, но, похоже, это гораздо лучший способ справиться с проблемой такого типа в целом