非构造函数的模式匹配

Pattern match over non-constructor functions

模式匹配和惰性求值的最强大方式之一是绕过昂贵的计算。然而,我仍然感到震惊 Haskell 只允许构造函数的模式匹配,这根本不是模式匹配!

有没有什么方法可以在 Haskell 中实现以下功能:

exp :: Double -> Double
exp 0 = 1
exp (log a) = a
--...

log :: Double -> Double
log 1 = 0
log (exp a) = a
--...

我发现这个有用的最初问题是在 Monoid 中编写关联偏好/规则 class:

class Monoid m where
  iden :: m
  (+) m -> m -> m

  (+) iden a = a
  (+) a iden = a

  --Line with issue
  (+) ((+) a b) c = (+) a ((+) b c)

没有理由对此感到震惊。在任意函数上进行模式匹配怎么可能甚至遥不可及?大多数函数都不是可逆的,即使对于那些可逆的函数,实际计算逆函数通常也很重要。

当然,编译器原则上可以处理一些琐碎的例子,比如用 x 替换文字 exp (log x),但这在实践中几乎完全没有用(在不太可能的情况下,有人会按字面意思写,他们也可以在源代码中减少它),并且如果内联顺序发生变化,无论编译器是否可以看到匹配应用,通常会导致非常奇怪的不可预测的行为。

(但是有一个叫做rewrite rules的东西,它与你提出的类似,但被视为只是一种优化工具。)

即使 Monoid class 中没有错误的两行也没有意义,但出于不同的原因。首先,当你写

  (+) iden a = a
  (+) a iden = a

这并不符合您的想法。这实际上是两个多余的 catch-call 子句,相当于

  (+) x y = y
  (+) x y = x

...这是一个完全荒谬的事情。你想表达的其实可以写成

  default (+) :: Eq a => a -> a -> a
  x+y
   | x==iden    = y
   | y==iden    = x
   | otherwise  = ...

...但这仍然没有完成任何有用的事情,因为这永远不会成为 + 的完整定义。一旦一个具体实例甚至开始定义它自己的 + 操作符,完整的 默认操作符将被忽略。

此外,如果您的 Haskell 项目中到处都是此类子句,那实际上意味着您执行了很多不必要的、多余的额外检查。一个守法的 Monoid 实例无论如何都需要满足 mempty <> a ≡ a ,没有必要明确地对其进行特殊处理。

我想你真正想要的是测试 在 class 声明中以可以自动检查的方式指定法律是有意义的,但标准 Haskell 没有语法。大多数项目只是在单独的测试套件中进行,使用 QuickCheck 生成示例输入。我想还有一个工具可以让你把测试用例放在你的源文件中,但我忘了它叫什么了。