当然,几乎任何东西都可以使用 pointfree 编程方式实现。麻烦的是,在结果表达式中允许哪些函数。如果我们进行模式匹配,则通常需要一个折叠函数来执行匹配。例如,如果我们对 Maybe a
进行模式匹配,我们需要用 maybe
替换它。同样,Either a b
模式可以使用 either
来表示。
请注意签名中的模式。
data Maybe a = Nothing | Just a
maybe :: b -> (a -> b) -> (Maybe a -> b)
Maybe
可能有两个构造函数,一个不带参数,另一个带有一个a
参数。所以maybe
需要两个参数:一个是0元函数(b
),另一个带有a
类型的参数(a -> b
),然后返回一个从Maybe a -> b
的函数。同样的模式也存在于either
中。
data Either a b = Left a | Right b
either :: (a -> c) -> (b -> c) -> (Either a b -> c)
两种情况。第一种情况接受一个 a
并产生我们想要的任何 c
。第二种情况接受一个 b
并产生我们想要的任何 c
。在每种情况下,我们希望有一个函数来处理和类型中的每个可能项。
为了系统地将像 \[x] -> x
这样的函数无点风格化,我们需要类似的折叠功能。[a]
被声明为基本上是......
data [a] = [] | a : [a]
因此,我们需要一个具有这个签名的函数。
list :: b -> (a -> [a] -> b) -> ([a] -> b)
现在,flip foldr
函数接近于
flip foldr :: b -> (a -> b -> b) -> ([a] -> b)
但它是递归的。它在 a : [a]
的 [a]
部分上调用了提供的函数。我们需要一个真正的折叠函数,这在Haskell的基本库中并没有提供。一个快速的Hoogle搜索告诉我们,这个函数确实存在于一个包中,叫做 extra
。当然,对于这个小例子,我们可以非常容易地自己编写它。
list :: b -> (a -> [a] -> b) -> ([a] -> b)
list f g x = case x of
[] -> f
(y:ys) -> g y ys
现在我们可以轻松地将它应用到你的\[x] -> x
中。首先,让我们写出你的函数实际执行的内容,包括所有混乱的undefined
情况(为了简洁起见,我将使用undefined
而不是长错误消息)。
func :: [a] -> a
func x = case x of
[] -> undefined
(y:ys) -> case ys of
[] -> y
(_:_) -> undefined
现在每个case语句都恰好匹配一个构造函数。这很适合转换成折叠。
func :: [a] -> a
func x = case x of
[] -> undefined
(y:ys) -> list y undefined ys
现在我们也要改变外壳
func :: [a] -> a
func x = list undefined (\y -> list y undefined) x
所以我们有:
func :: [a] -> a
func = list undefined (\y -> list y undefined)
或者,如果我们想真正疯狂一些
func :: [a] -> a
func = list undefined (flip list undefined)
但是这个函数不在基本库中
是的,这是真的。我们有点作弊,使用了一个不存在的fold函数。如果我们想系统地做到这一点,我们需要那个fold操作符。但是即使没有它,我们仍然可以用foldr1
来凑合,这足以满足我们特定的目的。
func' :: [a] -> a
func' = foldr1 (const (const undefined))
因此,回答你的问题,在没有合适签名的折叠函数的情况下,我们不能总是像在你的例子中那样系统地替换模式匹配成为点自由风格编程范式。幸运的是,这个函数总是可以编写出来,针对任何Haskell 98数据类型(可能也包括GADTs,但我还没有深入考虑过这种可能性)。但即使没有这种支持,我们仍然可以让它正常工作,某种程度上。
only
函数可以解决问题:https://hackage.haskell.org/package/ghc-8.10.1/docs/Util.html#v:only 但是在非调试模式下,为了提高性能,它等同于head
函数。 - Willem Van Onsem