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或类似的东西?或者你会认为这种风格是惯用的吗?
首先,这种风格实际上是相当惯用的.由于你对两个不同的值做两件事,所以有一些不可简化的复杂性; 实际的模式匹配本身并没有多少介绍.此外,我个人发现大多数时候显式风格非常易读.
但是,还有另一种选择.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更好.
| 归档时间: |
|
| 查看次数: |
244 次 |
| 最近记录: |