我可以避免在Haskell中“向右漂移”吗?

时间:2011-08-10 09:21:31

标签: coding-style haskell

当我使用命令式语言时,我经常编写像

这样的代码
foo (x) {
    if (x < 0) return True;
    y = getForX(x);
    if (y < 0) return True;

    return x < y;
}

也就是说,我一个接一个地检查条件,尽快突破区块 尽可能。

我喜欢这个,因为它使代码“平淡”并遵循“结束”的原则 重量“。我认为它更具可读性。

但是在Haskell中我会把它写成

foo x = do
    if x < 0
        then return x
        else do
            y <- getForX x

            if y < 0
                then return True
                else return $ x < y

我不喜欢哪个。我可以使用允许爆发的monad,但是 因为我已经使用了monad,所以我必须lift一切,这会增加单词 如果可以,我想避免。

我认为这并不是一个完美的解决方案,但有人有 有什么建议吗?

4 个答案:

答案 0 :(得分:13)

针对您的具体问题:悬挂do符号和逻辑用法怎么样?

foo x = do
  if x < 0 then return x else do
  y <- getForX x
  return $ y < 0 || x < y

修改

结合hammar的说法,你甚至可以得到更漂亮的代码:

foo x | x < 0     = return x
      | otherwise = do y <- getForX x
                       return $ y < 0 || x < y

答案 1 :(得分:12)

使用模式和警卫可以提供很多帮助:

foo x | x < 0 = return x
foo x = do
    y <- getForX x
    if y < 0
        then return True
        else return $ x < y

您还可以在where子句中引入小帮助函数。这也有助于提高可读性。

foo x | x < 0 = return x
foo x = do
    y <- getForX x
    return $ bar y
  where
    bar y | y < 0     = True
          | otherwise = x < y

(或者如果代码真的像这个例子一样简单,请使用FUZxxl建议的逻辑)。

答案 2 :(得分:7)

执行此操作的最佳方法是使用警卫,但是您需要先使用y值才能在警卫中使用它。这需要从getForX获得,可能隐藏在某些monad中,除非通过getForX(例如IO monad),否则你无法获得价值,然后你必须提升纯函数那个使用守卫进入那个monad。一种方法是使用liftM

foo x = liftM go (getForX x)
  where
    go y | x < 0     = True
         | y < 0     = True
         | otherwise = x < y

答案 3 :(得分:3)

不仅仅是

foo x = x < y || y < 0 where y = getForX x
编辑:正如Owen指出的那样 - getForX是monadic所以我上面的代码不起作用。以下版本可能应该:

foo x = do
  y <- getForX x
  return (x < y || y < 0)