Могу ли я избежать "правильного дрейфа" в 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)