避免递归中的模式匹配

fir*_*dle 4 haskell applicative

考虑一下我用来解决欧拉问题58的代码:

diagNums = go skips 2
    where go (s:skips) x = let x' = x+s
                           in x':go skips (x'+1)

squareDiagDeltas = go diagNums
    where go xs = let (h,r) = splitAt 4 xs
                  in h:go r
Run Code Online (Sandbox Code Playgroud)

我不喜欢第二个函数中的模式匹配.它看起来比必要的复杂!这对我来说经常出现.在这里,splitAt返回一个元组,所以我必须先解构它才能递归.当我的递归本身返回我想要修改的元组时,相同的模式可能更令人讨厌.考虑:

f n = go [1..n]
    where go [] = (0,0)
          go (x:xs) = let (y,z) = go xs
                      in (y+x, z-x)
Run Code Online (Sandbox Code Playgroud)

与简单的递归相比:

f n = go [1..n]
    where go [] = 0
          go (x:xs) = x+go xs
Run Code Online (Sandbox Code Playgroud)

当然,这里的功能纯属无意义,可以用完全不同的更好的方式编写.但我的观点是,每次我需要通过递归回调多个值时,就会出现模式匹配的需要.

有没有办法避免这种情况,可能是通过使用Applicative或类似的东西?或者你会认为这种风格是惯用的吗?

Tik*_*vis 6

首先,这种风格实际上是相当惯用的.由于你对两个不同的值做两件事,所以有一些不可简化的复杂性; 实际的模式匹配本身并没有多少介绍.此外,我个人发现大多数时候显式风格非常易读.

但是,还有另一种选择.Control.Arrow有一堆用于处理元组的函数.由于功能箭头->也是Arrow如此,所有这些都适用于正常功能.

因此,您可以使用(***)两个函数组合来处理元组,从而重写第二个示例.此运算符具有以下类型:

(***) :: a b c -> a b' c' -> a (b, b') (c, c')
Run Code Online (Sandbox Code Playgroud)

如果我们替换a->,我们得到:

(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c'))
Run Code Online (Sandbox Code Playgroud)

所以,你可以结合(+ x)(- x)与一个单一的功能(+ x) *** (- x).这相当于:

\ (a, b) -> (a + x, b - x)
Run Code Online (Sandbox Code Playgroud)

然后你可以在你的递归中使用它.不幸的是,-运算符是愚蠢的,不能分段工作,所以你必须用lambda写它:

(+ x) *** (\ a -> a - x) $ go xs 
Run Code Online (Sandbox Code Playgroud)

显然你可以想象使用任何其他运算符,所有运算符都不是那么愚蠢:).

老实说,我认为这个版本的可读性低于原版.但是,在其他情况下,***版本可以更具可读性,因此了解它很有用.特别是,如果你传递的(+ x) *** (- x)是高阶函数而不是立即应用它,我认为***版本会比显式lambda更好.

  • 可以用`(+( - x))`或`(减去x)`替换你的'a`lambda或存在一些问题?谢谢!:) (3认同)