Haskell的等效模式是什么,如果在命令式语言中落空,例如:
function f (arg, result) {
if (arg % 2 == 0) {
result += "a"
}
if (arg % 3 == 0) {
result += "b"
}
if (arg % 5 == 0) {
result += "c"
}
return result
}
答案 0 :(得分:14)
您也可以使用State
monad,而不是使用Writer
monad,并利用String
的{{1}}个实例(真的Monoid
' s [a]
实例):
Monoid
我认为它非常简洁,干净,简单。
这比import Control.Monad.Writer
f :: Int -> String -> String
f arg result = execWriter $ do
tell result
when (arg `mod` 2 == 0) $ tell "a"
when (arg `mod` 3 == 0) $ tell "b"
when (arg `mod` 5 == 0) $ tell "c"
monad的一个优点是你可以通过重新排列行来重新排列连接发生的顺序。例如,如果您想运行State
并退出f 30 "test"
,您只需要交换"atestbc"
的前两行:
do
而在f arg result = execWriter $ do
when (arg `mod` 2 == 0) $ tell "a"
tell result
when (arg `mod` 3 == 0) $ tell "b"
when (arg `mod` 5 == 0) $ tell "c"
monad中你必须改变操作:
State
因此,不必在输出字符串中执行顺序和顺序之间存在关系,而是必须仔细检查实际操作(f arg = execState $ do
when (arg `mod` 2 == 0) $ modify ("a" ++)
when (arg `mod` 3 == 0) $ modify (++ "b")
when (arg `mod` 5 == 0) $ modify (++ "c")
和(++ "a")
之间存在细微差别),而{{{在我看来,乍一看,代码非常清晰。
正如@JohnL指出的那样,这不是一个有效的解决方案,因为在Haskell ("a" ++)
上的连接速度不是很快,但您可以很容易地使用Writer
和Strings
来获取围绕这个:
Text
除了转换为更有效的类型之外,算法没有真正的改变。
答案 1 :(得分:10)
如果我们愿意在某种程度上模糊原始命令版本的逻辑,那么该函数可以非常简洁地编写:
f :: Int -> String -> String
f arg = (++ [c | (c, n) <- zip "abc" [2, 3, 5], mod arg n == 0])
Monad理解可以很好地再现原始逻辑:
{-# LANGUAGE MonadComprehensions #-}
import Data.Maybe
import Data.Monoid
f :: Int -> String -> String
f arg res = maybe res (res++) $
["a" | mod arg 2 == 0]
<> ["b" | mod arg 3 == 0]
<> ["c" | mod arg 5 == 0]
但是,它不是一种非常常用的语言扩展。幸运的是我们(在评论中给ØrjanJohansen提示),列表monad已经有了内置的理解糖,我们也可以在这里使用:
f :: Int -> String -> String
f arg res = res ++
['a' | mod arg 2 == 0]
++ ['b' | mod arg 3 == 0]
++ ['c' | mod arg 5 == 0]
答案 2 :(得分:6)
使用State monad作为Jan Dvorak的评论建议:
import Control.Monad.State
f :: Int -> String -> String
f arg = execState $ do
when (arg `mod` 2 == 0) $ modify (++ "a")
when (arg `mod` 3 == 0) $ modify (++ "b")
when (arg `mod` 5 == 0) $ modify (++ "c")
答案 3 :(得分:6)
我认为简短的回答是Haskell的落后方法是Monoids
。无论什么时候想要将许多事情合并为一件事,请考虑Monoids
。增加是一个很好的例子:
1 + 2 + 4 + 0 + 3 = 10.
添加数字时,这是一个无操作值0
。您可以随时添加它,但不会更改结果。 Monoids概括了这个概念,Haskell调用了无操作值mempty
。这就是你从你的组合中删除项目的方式(在你的例子中,你要删除不均匀划分的值)。 +
是组合器。 Haskell称之为mappend
。它有一个简写符号:<>
。
乘法是一个Monoid,mempty
值是1
,组合器是*
。
字符串也是一个Monoid。 mempty
值为""
,合并器为++
;
所以这里是使用Monoids的一个非常简单的函数实现:
import Data.Monoid
f :: Int -> String -> String
f arg str = str <> modsBy 2 "a" <> modsBy 3 "b" <> modsBy 5 "c"
where
modsBy n v = if arg `mod` n == 0 then v else mempty
巧妙的是,由于Monoids概括了这个概念,你可以非常容易地推广这个函数,因此它可以构建任何Monoid,而不仅仅是一个字符串。例如,你可以传入一个除数,幺半对和一些初始幺半群的列表来开始,每当除数均分,你就加上幺半群:
f :: Monoid a => Int -> a -> [(Int, a)] -> a
f arg initial pairs = initial <> mconcat (map modsBy pairs)
where
modsBy (n, v) = if arg `mod` n == 0 then v else mempty
mconcat
只是将Monoids列表组合在一起。
因此,您的初始示例现在可以像:
一样运行> f 10 "foo" [(2,"a"), (3,"b"), (5,"c")]
"fooac"
但你可以很容易地建立一个数字:
> f 10 1 [(2,1), (3,2), (5,3)]
5
关于Haskell的一个好处是它捕获并概括了许多我甚至没有意识到的概念。 Monoids
非常方便,整个应用程序架构都可以基于它们构建。
答案 4 :(得分:5)
有很多方法可以做到这一点。您可以做的一件事是将每个if
表示为Int -> Maybe Char
中的函数,然后将Maybe Char
的列表连接到最终字符串中:
maybeMod :: Int -> a -> Int -> Maybe a
maybeMod d v i = if i `mod` d == 0 then Just v else Nothing
f :: Int -> String
f i = mapMaybe ($ i) [maybeMod 2 'a', maybeMod 3 'b', maybeMod 5 'c']
答案 5 :(得分:4)
IMO你应该完全解决这个问题。你正在测试一系列非常相似的条件;那些应该保持在一起,而不是分散在一堆没有明显关系的条件上。为什么不将它们列入清单!因为每个mod选项都应该触发另一个信号字符,你需要一个关联列表。所以你从[(2,'a'),(3,'b'),(5,'c')]
开始。 (如果更多这些,例如10,请使用take 10 $ zip primes ['a'..]
,并列出所有素数!)
现在我们需要为每个条目决定:如果给定的数字可以被素数除,则返回该字符,否则不添加任何内容。这是一个非常简洁的列表理解:
f :: Int -> String
f arg = [ signal | (signal, n) <- [(2,'a'),(3,'b'),(5,'c')]
, arg `mod` n == 0 ]
答案 6 :(得分:1)
import Control.Arrow
f :: Int -> String -> String
f arg = (if arg `mod` 2 == 0 then (++"a") else id) >>>
(if arg `mod` 3 == 0 then (++"b") else id) >>>
(if arg `mod` 5 == 0 then (++"c") else id)
在这种情况下,>>>
只是flip (.)
。