如果跌倒

Saw*_*yer 7 haskell

什么是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单子,并乘虚而入StringMonoid实例(真正[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上的连接速度不是很快,但你可以很容易地使用TextBuilder解决这个问题:

{-# 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)

  • 实际上普通的列表推导也与你的monad理解类似:`f arg res = res ++ ['a'| mod arg 2 == 0] ++ ['b'| mod arg 3 == 0] ++ ['c'| mod arg 5 == 0]` (3认同)

Wyz*_*a-- 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")
Run Code Online (Sandbox Code Playgroud)

  • 我建议使用Writer monad代替`tell`而不是`modify.翻转(++)`. (3认同)

jhi*_*ner 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
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非常方便,整个应用程序架构都可以构建在它们之上.


Lee*_*Lee 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']
Run Code Online (Sandbox Code Playgroud)