非构造函数的模式匹配

1 language-features haskell pattern-matching

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

有没有办法在 Haskell 中实现以下功能:

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

log :: Double -> Double
log 1 = 0
log (exp a) = a
--...
Run Code Online (Sandbox Code Playgroud)

我发现这很有用的最初问题是在 Monoid 类中编写关联性首选项/规则:

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

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

  --Line with issue
  (+) ((+) a b) c = (+) a ((+) b c)
Run Code Online (Sandbox Code Playgroud)

lef*_*out 8

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

\n

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

\n

(然而,有一种叫做重写规则的东西,它与您提出的类似,但仅被视为一种优化工具。)

\n

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

\n
  (+) iden a = a\n  (+) a iden = a\n
Run Code Online (Sandbox Code Playgroud)\n

这并不像你想象的那样。这实际上是两个多余的catch-call子句,相当于

\n
  (+) x y = y\n  (+) x y = x\n
Run Code Online (Sandbox Code Playgroud)\n

……这是一件完全无意义的事情。你想要说的实际上可以写成

\n
  default (+) :: Eq a => a -> a -> a\n  x+y\n   | x==iden    = y\n   | y==iden    = x\n   | otherwise  = ...\n
Run Code Online (Sandbox Code Playgroud)\n

...但这仍然没有完成任何有用的事情,因为这永远不会成为+. 一旦具体实例开始定义自己的+运算符,完全默认的运算符就会被忽略。

\n

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

\n

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

\n

  • @DanielWagner 这不会破坏引用透明度吗?如果 `fx` 的处理方式与 `f (id x)` 不同,我认为我们有问题。这可以通过要求程序员提供证明来解决,证明重写规则是汇合的。一般来说,编写这样的证明看起来是一项艰巨的任务。 (2认同)