Warning: file_get_contents(/data/phpspider/zhask/data//catemap/1/cassandra/3.json): failed to open stream: No such file or directory in /data/phpspider/zhask/libs/function.php on line 167

Warning: Invalid argument supplied for foreach() in /data/phpspider/zhask/libs/tag.function.php on line 1116

Notice: Undefined index: in /data/phpspider/zhask/libs/function.php on line 180

Warning: array_chunk() expects parameter 1 to be array, null given in /data/phpspider/zhask/libs/function.php on line 181
Haskell 避免递归中的模式匹配_Haskell_Applicative - Fatal编程技术网

Haskell 避免递归中的模式匹配

Haskell 避免递归中的模式匹配,haskell,applicative,Haskell,Applicative,考虑我用来解决Euler问题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 我不喜欢第二个函数中的模式匹

考虑我用来解决Euler问题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
我不喜欢第二个函数中的模式匹配。这看起来太复杂了!这是我经常遇到的事情。这里,
splitAt
返回一个元组,所以我必须先对它进行分解,然后才能递归。当我的递归本身返回一个我想要修改的元组时,同样的模式可能会出现,甚至更令人烦恼。考虑:

f n = go [1..n]
    where go [] = (0,0)
          go (x:xs) = let (y,z) = go xs
                      in (y+x, z-x)
与简洁的递归相比:

f n = go [1..n]
    where go [] = 0
          go (x:xs) = x+go xs
当然,这里的函数完全是胡说八道,可以用完全不同的更好的方式编写。但我的观点是,每当我需要通过递归返回多个值时,就需要进行模式匹配


有没有办法避免这种情况,可以使用
Applicative
或类似的方法?或者你认为这种风格是惯用的吗?因为你对两种不同的价值观做了两件事,所以有一些不可简化的复杂性;实际的模式匹配本身不会引入太多内容。此外,我个人觉得这种直白的风格在大多数时候都非常可读

然而,还有一种选择<代码>控件。箭头有一系列用于处理元组的函数。由于功能箭头
->
也是
箭头
,因此所有这些都适用于正常功能

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

(***) :: a b c -> a b' c' -> a (b, b') (c, c')
如果我们将
a
替换为
->
,我们将得到:

(***) :: (b -> c) -> (b' -> c') -> ((b, b') -> (c, c'))
因此,您可以将
(+x)
(-x)
组合成一个带有
(+x)***(-x)
的函数。这相当于:

\ (a, b) -> (a + x, b - x)
然后可以在递归中使用它。不幸的是,
-
运算符很愚蠢,不能在节中工作,因此您必须使用lambda编写它:

(+ x) *** (\ a -> a - x) $ go xs 
显然,您可以想象使用任何其他运算符,所有这些运算符都不那么愚蠢:)


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

我同意Tikhon Jelvis的观点,即您的版本没有问题。正如他所说,使用Control.Arrow中的组合符对高阶函数很有用。您可以使用折页书写
f

f n = foldr (\x -> (+ x) *** subtract x) (0,0) [1..n]
如果您真的想去掉
squarediagdelatas
中的
let
(我不确定我会这么做),可以使用
second
,因为您只修改元组的第二个元素:

squareDiagDeltas = go diagNums
  where go = uncurry (:) . second go . splitAt 4
我同意

您还可以在
诊断中取消模式匹配:

diagNums = go skips 2
    where go (s:skips) x = let x' = x+s
                           in x':go skips (x'+1)
diagNums = zipWith (+) [2..] $ scanl1 (+) skips
递归使我们很难判断这里发生了什么,所以让我们 深入研究它

假设
skips=s0:s1:s2:s3:…
,那么我们有:

diagNums = go skips 2
         = go (s0 : s1 : s2 : s3 : ...) 2 
         = s0+2 : go (s1 : s2 : s3 : ... ) (s0+3)
         = s0+2 : s0+s1+3 : go (s2 : s3 : ... ) (s0+s1+4) 
         = s0+2 : s0+s1+3 : s0+s1+s2+4 : go (s3 : ... ) (s0+s1+s2+5) 
         = s0+2 : s0+s1+3 : s0+s1+s2+4 : s0+s1+s2+s3+5 : go (...) (s0+s1+s2+s3+6) 
这让事情变得更清楚了,我们得到了两个序列的和,这很容易用
zipWith(+)
计算:

所以现在我们只需要找到一种更好的方法来计算
跳过的部分和
,这对于:

诊断留下一个(IMO)更容易理解的定义

diagNums = go skips 2
    where go (s:skips) x = let x' = x+s
                           in x':go skips (x'+1)
diagNums = zipWith (+) [2..] $ scanl1 (+) skips

可能会用
(+(-x))
(减去x)
替换
a
lambda,或者存在一些问题?谢谢!:)@是的,你也可以做这两件事。我不喜欢任何一种选择:P,所以是箭头。我突然想起了这个想法。你是对的,现在还不清楚它是否比原来的好,但它最直接、最清楚地回答了我的问题,所以我接受你的回答。另一个选项:
squareDiagDeltas=unfover(Just.splitAt 4)diagNums
它通过第二次尝试读起来既有趣又有创意,所以+1,谢谢!