如果跌倒

时间:2014-05-02 14:50:51

标签: 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
}

7 个答案:

答案 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" ++)上的连接速度不是很快,但您可以很容易地使用WriterStrings来获取围绕这个:

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 (.)