Can I avoid “rightward drift” in Haskell?

感情迁移 提交于 2020-01-03 07:18:12

问题


When I use an imperative language I often write code like

foo (x) {
    if (x < 0) return True;
    y = getForX(x);
    if (y < 0) return True;

    return x < y;
}

That is, I check conditions off one by one, breaking out of the block as soon as possible.

I like this because it keeps the code "flat" and obeys the principle of "end weight". I consider it to be more readable.

But in Haskell I would have written that as

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

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

Which I don't like as much. I could use a monad that allows breaking out, but since I'm already using a monad I'd have to lift everything, which adds words I'd like to avoid if I can.

I suppose there's not really a perfect solution to this but does anyone have any advice?


回答1:


For your specific question: How about dangling do notation and the usage of logic?

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

Edit

Combined with what hammar said, you can even get more beautiful code:

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



回答2:


Using patterns and guards can help a lot:

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

You can also introduce small helper functions in a where clause. That tends to help readability as well.

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

(Or if the code really is as simple as this example, use logic as FUZxxl suggested).




回答3:


The best way to do this is using guards, but then you need to have the y value first in order to use it in the guard. That needs to be gotten from getForX wich might be tucked away into some monad that you cannot get the value out from except through getForX (for example the IO monad) and then you have to lift the pure function that uses guards into that monad. One way of doing this is by using liftM.

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



回答4:


Isn't it just

foo x = x < y || y < 0 where y = getForX x

EDIT: As Owen pointed out - getForX is monadic so my code above would not work. The below version probably should:

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


来源:https://stackoverflow.com/questions/7008493/can-i-avoid-rightward-drift-in-haskell

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!