非构造函数的模式匹配
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 生成示例输入。我想还有一个工具可以让你把测试用例放在你的源文件中,但我忘了它叫什么了。
模式匹配和惰性求值的最强大方式之一是绕过昂贵的计算。然而,我仍然感到震惊 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 生成示例输入。我想还有一个工具可以让你把测试用例放在你的源文件中,但我忘了它叫什么了。