Haskell,结合Do和if

时间:2015-12-13 18:31:33

标签: haskell

您好我想知道是否有办法以这种方式在haskell中使用do if

doIfFunction :: Eq a => [a]->[a] ->[a]
doIfFunction l1 l2 = do
     if l /= something then (l2++something) else l2
     if l /= something2 then (l2++something2) else l2
     l2

基本上如果这个函数返回一个不同的值,我希望它将它添加到l2然后返回l2。我一直认为l2是空的,而不应该是,这是因为else l2是否将l2的值重置为开始时的值?

如果是这样,我可以做这样的事情吗?

doIfFunction :: Eq a => [a]->[a] ->[a]
doIfFunction l1 l2 = do
     if l /= something then (let l2 = l2++something) else l2
     if l /= something2 then (let l2 = l2++something2) else l2
     l2

这会产生错误,但我想知道这是否在正确的位置。

在调用doIfFunction时,doIfFunction l []始终为l,其中[]包含值且emptygsub

2 个答案:

答案 0 :(得分:4)

通常,您应该避免在Haskell中考虑修改任何内容。事实上,你不能,语言不允许它。完全

您可以考虑创建l2修改版的其他列表,而不是修改列表l2本身。 (直接的好处是你仍然可以在其他任何地方使用原始的l2;特别是如果其他一些函数/线程仍然需要这个旧版本并且你不知道它的话就没问题。)

即,而不是

 do
   modify_something_about_l2
   modify_something_else_about_l2
   yield_some_result

你想评估函数应用程序链的结果,比如

f l2 = some_other_modification (some_modification (l2))

或者,我们更喜欢写它,

f = some_other_modification . some_modification

在您的特定情况下:

doIfFunction l1
  = (\l2 -> if l /= something2 then (l2++something2) else l2)
    . (\l2 -> if l /= something then (l2++something) else l2)

如果您不喜欢“向后风格”,您还可以将合成运算符.替换为其翻转版本:

import Control.Arrow

doIfFunction l1
  = (\l2 -> if l /= something then (l2++something) else l2)
   >>> (\l2 -> if l /= something2 then (l2++something2) else l2)

此外,您可以通过eta-reduction避免提及中间名单:

doIfFunction l1
  = (if l /= something then (++something) else id)
   >>> (if l /= something2 then (++something2) else id)

那说......

您可能在想:始终创建所有内容的新修改版本,而不仅仅是就地修改,效率很低。好吧,有时它通常实际上没有问题。

您的示例实际上是一个问题:要将something附加到l2,需要制作整个列表的副本。也许你可以很容易地避免这个问题:如果不是追加,你 prepend

doIfFunction l1
  = (if l /= something then (something++) else id)
   >>> (if l /= something2 then (something2++) else id)

然后没有性能损失。原因是:列表只是一个元素链(头),每个元素都引用了列表的其余部分。 预先 - 添加一个元素只需要创建一个新头并将其链接到现有列表!

即使使用聪明的懒惰前置,有时纯功能样式 显着更加耗时。不要担心:虽然Haskell不允许修改值,但有些类型封装了破坏性修改的概念,即ST monad。因此,你仍然可以有效地实现需要破坏性更新的算法,但基本上你不是在Haskell中,而是在嵌入式“特定于域的命令式语言”中,它与Haskell无缝集成。

答案 1 :(得分:3)

只需将if子句放在let语句中:

doIfFunction :: Eq a => [a] -> [a] -> [a]
doIfFunction l1 l2 = do
    let l2'  = if l /= something  then l2  ++ something  else l2
    let l2'' = if l /= something2 then l2' ++ something2 else l2'
    l2''

但是在这种情况下,由于不使用任何monadic操作,因此您不需要do表示法。我相信你想用do符号来模拟命令式编程。如果您认为它更具可读性,请考虑使用where

doIfFunction :: Eq a => [a] -> [a] -> [a]
doIfFunction l1 l2 = result
    where newL2 = if l /= something  then l2 ++ something  else l2
        result = if l /= something2 then newL2 ++ something2 else newL2

或者,当您对Haskell更熟悉时,您可以使用leftaroundabout's answer(这将是写出您想要做的事情的最惯用的方式)。