Могу ли я избежать "правильного дрейфа" в Haskell?

Когда я использую императивный язык, я часто пишу код вроде

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

    return x < y;
}

То есть, я проверяю условия один за другим, вырываясь из блока, как только насколько это возможно.

Мне это нравится, потому что он держит код "плоским" и подчиняется принципу "end" вес ". Я считаю, что это более читаемо.

Но в 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

Что мне не нравится. Я мог бы использовать монаду, которая позволяет вырваться, но так как я уже использую монаду, мне придется lift все, что добавляет слова Я бы хотел избежать, если смогу.

Я полагаю, что на самом деле это не идеальное решение, но у кого-то есть любые советы?

Ответ 1

Для вашего конкретного вопроса: как насчет обозначения do и использования логики?

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

Изменить

В сочетании с тем, что сказал хаммар, вы даже можете получить более красивый код:

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

Ответ 2

Использование шаблонов и охранников может помочь много:

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

Ответ 3

Лучший способ сделать это - использовать охранники, но сначала вам нужно иметь значение y, чтобы использовать его в защитнике. Это нужно получить от getForX, который может быть спрятан в какую-нибудь монаду, из которой вы не можете получить ценность, кроме как через getForX (например, монаду IO), а затем вам нужно поднять чистую функцию, это монада. Один из способов сделать это - использовать liftM.

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

Ответ 4

Разве это не просто

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

EDIT: Как отметил Оуэн, - getForX является монадическим, поэтому мой код выше не будет работать. Возможно, следующая версия должна:

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