在Haskell中使用函数作为字段从数据类型派生Eq时出现的问题

Problem when deriving Eq from data type with function as field in Haskell

提问人:Javier Sánchez Castro 提问时间:3/10/2022 最后编辑:Javier Sánchez Castro 更新时间:3/11/2022 访问量:208

问:

我正在尝试使用函数作为字段从数据类型派生 Eq,但没有按预期工作。

我也尝试编写te实例,但仍然不起作用

data Conf = Conf {
    rule :: ([Char] -> Char),
    start :: Int,
    numLines :: Double,
    window :: Int,
    move :: Int,
    actualLine :: Int,
    lastLine :: String
} deriving (Eq)

这是一个以印刷图形形式包含 wolfram 金字塔的项目,例如,规则是:

rule30 :: [Char] -> Char
rule30 "***" = ' '
rule30 "** " = ' '
rule30 "* *" = ' '
rule30 "*  " = '*'
rule30 " **" = '*'
rule30 " * " = '*'
rule30 "  *" = '*'
rule30 "   " = ' '
rule30 _     = ' '

有很多规则要遵循,正是出于这个原因,我想直接将“函数指针”保存在 Conf 数据类型中。

那么,为什么我需要导数(方程)? 我需要它,因为在主要情况下,我检查是否为“无”(错误处理检查,例如,如果用户设置了错误的规则......

错误消息:

src/Wolf.hs:24:13: error:
• No instance for (Eq ([Char] -> Char))
    arising from the first field of ‘Conf’ (type ‘[Char] -> Char’)
    (maybe you haven't applied a function to enough arguments?)
  Possible fix:
    use a standalone 'deriving instance' declaration,
      so you can specify the instance context yourself
• When deriving the instance for (Eq Conf)
   |
24 | } deriving (Eq)
   |             ^^

我错过了什么?

Haskell 方法 函数式编程 等式 推导

评论


答:

5赞 leftaroundabout 3/10/2022 #1

是什么让你认为这是可能的?如果您的类型包含函数字段,则比较您的类型的值是否相等至少与比较函数是否相等一样困难。但是要检查两个函数是否相等(在 Haskell 中,唯一合理的含义是扩展相等),您需要检查它们在所有可能的输入上是否一致。这是一件完全不可行的事情,即使对于简单的输入也是如此,但如果参数具有类型,则肯定是 .Int[Char]

那么,为什么我需要?我需要它,因为在我检查是否是deriving(Eq)mainNothing

完全不需要那个!测试值是否为“使用”是无效的,即使在可能的情况下也是如此。您应该改用任一模式匹配EqMaybeNothing==

main = do
   ...
   let myConfq = ... :: Maybe Conf
   case myConfq of
     Nothing -> error "Meh, couldn't have conf"
     Just conf -> ...

...或者使用更高级别的组合器,可能基于 s 或实例MaybeApplicativeTraversable

import Data.Traversable

main = do
   ...
   let myConfq = ... :: Maybe Conf
   traverse ... myConfq

评论

0赞 Robin Zigmond 3/11/2022
“通过使用 == 来测试 Maybe 值是否为 Nothing 是一种非常低效的方法,即使在那些可能的类型上也是如此” 虽然我同意你不应该以这种方式检查它,但我认为这不是一个正当的理由。如果有一个实例,那么使用 on 值将简单地以与手动相同的方式使用模式匹配,所以我看不出它的效率会降低多少。这是错误的 imo 主要是因为它迫使你有一个实例,而这实际上是完全不必要的——正如你已经解释的那样。aEq== NothingMaybe aEqa
2赞 leftaroundabout 3/11/2022
@RobinZigmond很公平,是的,它本身就足够快了。问题只是,这并不能让您访问案例中包含的值,这很可能也是必需的。— 真正低效的是用 .同样,将其更改为可以避免不必要的遍历整个事情,但这仍然可能不是要走的路。(== Nothing)Justlength l == 0l == []
3赞 Iceland_jack 3/10/2022 #2

我正在考虑允许注释数据类型字段的想法,这些字段将允许你想要的东西: 通过字段:派生中更精细的粒度

这个想法是定义一个比较总是成功的 newtype:

newtype Ignore a = Ignore a

instance Eq (Ignore a) where
  _ == _ = True

instance Ord (Ignore a) where
  compare _ _ = EQ

然后只注释函数字段;然后,当我们派生数据类型的实例时,操作字段的实例实际上是通过 newtype (==) @(via Ignore) 执行的:(==) @([Char] -> Char)

data Conf = Conf
  { rule  :: [Char] -> Char
         via Ignore ([Char] -> Char)
  , start :: Int
  , ..
  }
  deriving
  stock (Eq, Ord)

评论

0赞 leftaroundabout 3/11/2022
虽然 Via Fields 是一件好事,但我强烈建议不要用它来拼凑一个实例。比较相等的两个值应该是相同的,如果结构上不相同,那么对于可以通过类型的公共接口观察到的任何内容,至少行为相同。我怀疑这里的情况是否如此。Eq
0赞 Iceland_jack 3/11/2022
没错,答案是针对技术的,而不是你的答案所解决的用例。该技术对于其他结构性不强的类更为常见,但即使对于相等、哈希和序列化,也有非黑客应用程序。类型包含 ID/指纹或其他字段包含元数据(如标记/类型/派生/注释或某些基元数组的长度和偏移量)的情况并不少见,这些元数据不考虑类型的相等性。