Как вы определяете класс типов Haskell с типом, который не может быть выведен?

Я использую библиотеку Reflex.Dom, которая определяет набор функций для создания элементов HTML DOM

  • el создает элемент
  • el' создает и возвращает элемент
  • elAttr создает элемент с заданными атрибутами
  • elAttr' создает и возвращает элемент с заданными атрибутами
  • так далее

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

{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE MultiParamTypeClasses #-}
{-# LANGUAGE OverloadedStrings #-}

module ElMaker where

import Data.Map (Map)
import qualified Data.Map as Map
import qualified Reflex.Dom as D

-- el: type of element to create
-- input: input parameter
-- output: return value
class (D.MonadWidget t m) => ElMaker t m el input output where
  el :: el -> input -> m output
  el e = elAttr e Map.empty

  elAttr :: el -> Map Text Text -> input -> m output
  elAttr e attrs input = snd <$> elAttr' e attrs input

  el' :: el -> input -> m (D.El t, output)
  el' e = elAttr' e Map.empty

  -- This is the only one to implement, yay!
  elAttr' :: el -> Map Text Text -> input -> m (D.El t, output)

Я создал экземпляр, который использует оригинал elAttr' чтобы проверить это. Это сработало:

import Data.Text (Text)
import qualified Reflex.Dom as D

instance (D.MonadWidget t m) => ElMaker t m Text (m output) output where
  elAttr' = D.elAttr'

И тогда я создал Button Экземпляр виджета, который возвращает событие, когда кнопка нажата. Это сработало:

data Button = Button
instance (MonadWidget t m) => ElMaker t m Button (m input) (Event t ()) where
  elAttr' _ attrs contents = do
    (e, _) <- D.el' "button" contents
    return $ (e, D.domEvent D.Click e)

Я хотел бы иметь возможность создавать виджеты, поэтому я попытался переписать Button экземпляр, чтобы использовать Text экземпляр ElMaker создать элемент. Но он не компилируется:

data Button = Button
instance (MonadWidget t m) => ElMaker t m Button (m input) (Event t ()) where
  elAttr' _ attrs contents = do
    (e, _) <- el' ("button" :: Text) contents
    return $ (e, D.domEvent D.Click e)

Выход компилятора:

MDL.hs:119:15: error:
    • Could not deduce (ElMaker t m Text (m input) output0)
        arising from a use of ‘el'’
      from the context: MonadWidget t m
        bound by the instance declaration at MDL.hs:116:10-71
      The type variable ‘output0’ is ambiguous
      Relevant bindings include
        contents :: m input (bound at MDL.hs:117:19)
        elAttr' :: Button
                   -> Map.Map Text Text -> m input -> m (D.El t, Event t ())
          (bound at MDL.hs:117:3)
      These potential instance exist:
        instance MonadWidget t m => ElMaker t m Text (m output) output
          -- Defined in ‘ElMaker’
    • In a stmt of a 'do' block:
        (e, _) <- el' ("button" :: Text) contents
      In the expression:
        do { (e, _) <- el' ("button" :: Text) contents;
             return $ (e, D.domEvent D.Click e) }
      In an equation for ‘elAttr'’:
          elAttr' _ attrs contents
            = do { (e, _) <- el' ("button" :: Text) contents;
                   return $ (e, D.domEvent D.Click e) }

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

1 ответ

Решение

Что вы, вероятно, хотите сделать (а это то, что вы очень часто хотите сделать; это становится чем-то вроде FAQ), это заменить конструктор в правой части => с ограничением равенства на левой стороне.

{-# LANGUAGE GADTs #-}

instance (D.MonadWidget t m, input ~ m output)
   => ElMaker t m Text input output where ...

instance (D.MonadWidget t m, input' ~ m input, output ~ Event t ())
   => ElMaker t m Button input' output where ...

Как только вы знаете, что вы строите Text или же ButtonВы хотите зафиксировать конкретный экземпляр, а затем определенные параметры класса, имеющие определенные формы. Поместив их в ограничения экземпляра, вы сможете это сделать.

Для конкретного случая здесь, когда вы знаете, что имеете дело с Text, вы знаете, какой экземпляр вы хотите использовать, и что вы можете рассчитать output сопоставляя на input, Вы хотите, чтобы GHC знал это, а не задавался вопросом, Text Экземпляр будет иметь другое отношение ввода / вывода.

Примечание: как правило, лучше всего использовать параметр критического класса, который определяет, что другие идут последними. Так что я бы сделал el последний параметр ElMaker, Это хорошо для получения нового типа, а также является общепринятым.

Другие вопросы по тегам