Можно ли избежать «дрейфа вправо» в Хаскеле? - PullRequest
11 голосов
/ 10 августа 2011

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

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

    return x < y;
}

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

Мне это нравится, потому что он сохраняет код «ровным» и подчиняется принципу «конечного веса».Я считаю, что это более читабельно.

Но в Хаскеле я написал бы это как

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 все, что добавляет слова, которых я бы хотел избежать, если смогу.

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

Ответы [ 4 ]

13 голосов
/ 10 августа 2011

Для вашего конкретного вопроса: как насчет висячей 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
12 голосов
/ 10 августа 2011

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

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

7 голосов
/ 10 августа 2011

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

foo x = liftM go (getForX x)
  where
    go y | x < 0     = True
         | y < 0     = True
         | otherwise = x < y
3 голосов
/ 10 августа 2011

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

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

РЕДАКТИРОВАТЬ: Как отметил Оуэн - getForX является монадическим, поэтому мой код выше не будет работать.Нижеследующая версия, вероятно, должна:

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