考虑我写的以下代码:
import Control.Monad
increasing :: Integer -> [Integer]
increasing n
| n == 1 = [1..9]
| otherwise = do let ps = increasing (n - 1)
let last = liftM2 mod ps [10]
let next = liftM2 (*) ps [10]
alternateEndings next last
where alternateEndings xs ys = concat $ zipWith alts xs ys
alts x y = liftM2 (+) [x] [y..9]
其中'increasing n
'应返回一个n位数字列表,其数字从左到右增加(或保持不变)。
有没有办法简化这个?在任何地方使用“let
”和“liftM2
”对我来说都很难看。我想我错过了关于列表monad的重要内容,但我似乎无法摆脱它们。
答案 0 :(得分:8)
好吧,就liftM
函数而言,我首选的方法是使用Control.Applicative
中定义的组合器。使用这些,你就可以写last = mod <$> ps <*> [10]
。来自ap
的{{1}}函数执行相同的操作,但我更喜欢中缀版本。
Control.Monad
和(<$>)
是这样的:(<*>)
将函数liftM2
转换为函数a -> b -> c
。普通m a -> m b -> m c
仅为liftM
,与(a -> b) -> (m a -> m b)
和fmap
相同。
如果您对多参数函数执行此操作会发生什么?它会将(<$>)
变成a -> b -> c -> d
。这是m a -> m (b -> c -> d)
或ap
进入的地方:他们所做的就是将(<*>)
变为m (a -> b)
。所以你可以按照你想要的方式为它添加字符串。
那就是说,Travis Brown是正确的,在这种情况下,你似乎并不需要上述任何一种。实际上,您可以大大简化您的功能:例如,m a -> m b
和last
都可以写为映射在同一列表上的单参数函数next
和{{ 1}}与ps
和zipWith
相同。所有这些映射都可以组合并下推到zip
函数中。这使得map
成为单参数函数,同时也消除了alts
。最后,alts
可以与zip
合并为concat
,如果愿意,也可以与map
合并。以下是最终结果:
concatMap
请注意,我从你那里获得的所有重构都是纯粹的语法,只应用对函数结果没有影响的转换。均衡推理和参考透明度很好!
答案 1 :(得分:5)
我认为你要做的是:
increasing :: Integer -> [Integer]
increasing 1 = [1..9]
increasing n = do p <- increasing (n - 1)
let last = p `mod` 10
next = p * 10
alt <- [last .. 9]
return $ next + alt
或者,使用“list comprehension”,这只是列表的特殊monad语法:
increasing2 :: Integer -> [Integer]
increasing2 1 = [1..9]
increasing2 n = [next + alt | p <- increasing (n - 1),
let last = p `mod` 10
next = p * 10,
alt <- [last .. 9]
]
列表monad中的想法是您使用“bind”(<-
)迭代值列表,let
根据您目前所拥有的值计算单个值当前的迭代。当您第二次使用bind时,迭代将从该点开始嵌套。
答案 2 :(得分:3)
当其中一个参数始终是单例列表时,使用liftM2
(或<$>
和<*>
)看起来很不寻常。为什么不使用map
?以下内容与您的代码完全相同:
increasing :: Integer -> [Integer]
increasing n
| n == 1 = [1..9]
| otherwise = do let ps = increasing (n - 1)
let last = map (flip mod 10) ps
let next = map (10 *) ps
alternateEndings next last
where alternateEndings xs ys = concat $ zipWith alts xs ys
alts x y = map (x +) [y..9]
答案 3 :(得分:3)
以下是我编写代码的方法:
increasing :: Integer -> [Integer]
increasing 1 = [1..9]
increasing n = let allEndings x = map (10*x +) [x `mod` 10 .. 9]
in concatMap allEndings $ increasing (n - 1)
我按照以下方式获得了此代码。我做的第一件事是使用模式匹配而不是守卫,因为它在这里更清晰。我接下来要做的就是消除liftM2
s。它们在这里是不必要的,因为它们总是被称为一个大小一个列表;在这种情况下,它与调用map
相同。因此liftM2 (*) ps [10]
仅为map (* 10) ps
,其他呼叫网站也是如此。不过,如果您想要liftM2
的一般替换,可以使用Control.Applicative
的{{1}}(仅<$>
}和fmap
替换<*>
1}}对于任何liftMn
:n
变为liftMn f a b c ... z
。它是否更好是品味的问题;我碰巧喜欢它。 1 但在这里,我们可以完全消除它。
我简化原始代码的下一个地方是f <$> a <*> b <*> c <*> ... <*> z
。你从来没有真正利用你在do ...
- 块中的事实,因此代码可以成为
do
从这里开始,到达我的代码主要涉及将所有let ps = increasing (n - 1)
last = map (`mod` 10) ps
next = map (* 10) ps
in alternateEndings next last
融合在一起。其中一个不是map
的剩余电话是map
。但由于您实际拥有zipWith
,因此您只能同时使用zipWith alts next last
和10*p
,因此我们可以在同一个函数中计算它们。这导致
p `mod` 10
这基本上就是我的代码:let ps = increasing (n - 1)
in concat $ map alts ps
where alts p = map (10*p +) [y `mod` 10..9]
应始终变为concat $ map ...
(顺便说一句,在列表monad中为concatMap
),我们只使用=<<
一次我们可以将其折叠,我更喜欢ps
到let
。
1:从技术上讲,这仅适用于where
s,因此,如果您正在使用尚未成为monad的monad,Applicative
为{{ 1}}和<$>
是`liftM`
。但是,所有的monad都可以成为适用的仿函数,其中很多都是。
答案 4 :(得分:2)
我认为在单独的参数中传递最后一位数字并使用列表会更清晰。
f a 0 = [[]]
f a n = do x <- [a..9]
k <- f x (n-1)
return (x:k)
num = foldl (\x y -> 10*x + y) 0
increasing = map num . f 1