什么是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
}
Run Code Online (Sandbox Code Playgroud)
bhe*_*ilr 14
而不是使用的State单子,你也可以使用Writer单子,并乘虚而入String的Monoid实例(真正[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"
Run Code Online (Sandbox Code Playgroud)
我认为它非常简洁,干净,简单.
这比Statemonad的一个优点是你可以通过重新排列行来重新排列连接发生的顺序.因此,例如,如果你想跑步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"
Run Code Online (Sandbox Code Playgroud)
而在Statemonad你必须改变操作:
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")
Run Code Online (Sandbox Code Playgroud)
因此,不要在输出字符串中执行顺序和顺序之间存在关系,而是必须仔细检查实际操作((++ "a")和之间存在细微差别("a" ++)),而Writer在我看来代码非常清晰.
正如@JohnL所指出的那样,这不是一个有效的解决方案,因为在Haskell Strings上的连接速度不是很快,但你可以很容易地使用Text并Builder解决这个问题:
{-# LANGUAGE OverloadedStrings #-}
import Data.Text.Lazy (Text)
import qualified Data.Text.Lazy as T
import qualified Data.Text.Lazy.Builder as B
import Control.Monad.Writer
f :: Int -> Text -> Text
f arg result = B.toLazyText . execWriter $ do
tellText result
when (arg `mod` 2 == 0) $ tellText "a"
when (arg `mod` 3 == 0) $ tellText "b"
when (arg `mod` 5 == 0) $ tellText "c"
where tellText = tell . B.fromLazyText
Run Code Online (Sandbox Code Playgroud)
因此,除了转换为更有效的类型之外,算法没有真正的变化.
And*_*ács 10
如果我们愿意在某种程度上模糊原始命令式版本的逻辑,那么该函数可以非常简洁地编写:
f :: Int -> String -> String
f arg = (++ [c | (c, n) <- zip "abc" [2, 3, 5], mod arg n == 0])
Run Code Online (Sandbox Code Playgroud)
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]
Run Code Online (Sandbox Code Playgroud)
但是,它不是一种非常常用的语言扩展.对我们来说幸运(在评论中给Ø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]
Run Code Online (Sandbox Code Playgroud)
使用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")
Run Code Online (Sandbox Code Playgroud)
我认为简短的回答是,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
Run Code Online (Sandbox Code Playgroud)
巧妙的是,由于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
Run Code Online (Sandbox Code Playgroud)
mconcat 只是将Monoids列表组合在一起.
所以你的初始例子现在可以运行如下:
> f 10 "foo" [(2,"a"), (3,"b"), (5,"c")]
"fooac"
Run Code Online (Sandbox Code Playgroud)
但你可以轻松地建立一个数字:
> f 10 1 [(2,1), (3,2), (5,3)]
5
Run Code Online (Sandbox Code Playgroud)
关于Haskell的一个好处是它捕获并概括了许多我甚至没有意识到的概念.Monoids非常方便,整个应用程序架构都可以构建在它们之上.
有很多方法可以做到这一点.您可以做的一件事是将每个表示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']
Run Code Online (Sandbox Code Playgroud)