haskell - 如何定义一个无法推导类型的 Haskell 类型类?

标签 haskell reflex

我正在使用the Reflex.Dom library ,它定义了一组用于创建 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 实例以使用 ElMakerText 实例来创建元素。但编译失败:

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) }

我认为这是因为该函数不会对限制其类型的值执行任何操作,并且编译器确实希望它具有具体的类型。但这个类型类并不关心该类型参数的值是什么。有什么办法可以编译这个吗?

最佳答案

您可能想要做的事情(这是您经常想做的事情;它正在成为常见问题解答)是将 => 右侧的构造函数替换为等式约束在左侧。

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

一旦您知道自己正在构建 TextButton,您就会想要提交给特定实例,然后提交给具有特定形状的某些类参数。将它们放入实例约束中即可让您做到这一点。

对于这里的特殊情况,一旦您知道您正在处理 Text,您就知道要使用哪个实例,并且可以通过以下方式计算输出匹配输入。您希望 GHC 知道这一点,而不是想知道其他 Text 实例是否会有不同的输入/输出关系。

注意:通常最好的关键类参数决定其他参数最后。所以我将 el 作为 ElMaker 的最后一个参数。这对于新类型的派生很有好处,而且也是常规的。

关于haskell - 如何定义一个无法推导类型的 Haskell 类型类?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/39936716/

相关文章:

haskell - Traversables 的自然法则是什么意思?

haskell - 如何仅使用 stack.yaml 将 ghci 与我的反射项目一起使用?

css - 使用 Ob 和 reflex 导入 CSS 文件时出现静态文件错误

haskell - 使用 Reflex.GI.Gtk,如何在事件中使用和强制评估动态

haskell - 带有反射 frp 的单个 svg 元素

debugging - Haskell 中的无限递归

haskell - 将两个 Int 值相除以获得 Float 的正确方法是什么?

haskell - 如何使用 Data.Binary 存储递归数据类型

forms - 使用 Digestive Functors 和 Snap 进行单元验证

haskell - 如何呈现列表 (`Dynamics t [a]` 的动态)?