Хороший стиль кодирования на Haskell блока управления if / else? - PullRequest
30 голосов
/ 24 сентября 2008

Я изучаю Haskell в надежде, что это поможет мне приблизиться к функциональному программированию. Ранее я в основном использовал языки с синтаксисом, подобным C, например C, Java и D.

У меня небольшой вопрос о стиле кодирования управляющего блока if / else, используемого в учебнике в Викиучебниках . Код выглядит следующим образом:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   if (read guess) < num
     then do putStrLn "Too low!"
             doGuessing num
     else if (read guess) > num
            then do putStrLn "Too high!"
                    doGuessing num
            else do putStrLn "You Win!"

Меня это смущает, потому что этот стиль кодирования полностью нарушает рекомендуемый стиль в C-подобных языках, где мы должны делать отступы if, else if и else в одном столбце.

Я знаю, что это просто не работает в Haskell, потому что было бы ошибкой разбора, если бы я вставил else в том же столбце, что и if.

А как насчет следующего стиля? Я думаю, что это намного яснее, чем выше. Но так как вышеперечисленное используется в Wikibooks и Yet Another Tutorial, который отмечен как «лучший учебник, доступный онлайн» на официальном сайте Haskell, я не уверен, является ли этот стиль кодирования соглашением в программах на Haskell.

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    if (read guess) < num then
        do 
            putStrLn "Too low!"
            doGuessing num
        else if (read guess) > num then do 
            putStrLn "Too high!"
            doGuessing num
        else do 
            putStrLn "You Win!"

Итак, мне интересно, какой стиль кодирования используется чаще - или есть другой стиль кодирования для этого фрагмента кода?

Ответы [ 8 ]

27 голосов
/ 19 января 2010

Стиль Haskell является функциональным, а не обязательным! Вместо того, чтобы «делать это потом», подумайте о комбинировании функций и описании того, что будет делать ваша программа, а не как.

В игре ваша программа запрашивает у пользователя предположение. Правильное предположение - победитель. В противном случае пользователь пытается снова. Игра продолжается, пока пользователь не угадает правильно, поэтому мы пишем, что:

main = untilM (isCorrect 42) (read `liftM` getLine)

При этом используется комбинатор, который многократно выполняет действие (getLine извлекает строку ввода, а read преобразует эту строку в целое число в этом случае) и проверяет ее результат:

untilM :: Monad m => (a -> m Bool) -> m a -> m ()
untilM p a = do
  x <- a
  done <- p x
  if done
    then return ()
    else untilM p a

Предикат (частично примененный в main) проверяет предположение относительно правильного значения и отвечает соответственно:

isCorrect :: Int -> Int -> IO Bool
isCorrect num guess =
  case compare num guess of
    EQ -> putStrLn "You Win!"  >> return True
    LT -> putStrLn "Too high!" >> return False
    GT -> putStrLn "Too low!"  >> return False

Действие до тех пор, пока игрок не угадает правильно:

read `liftM` getLine

Почему бы не сделать это простым и просто составить две функции?

*Main> :type read . getLine

<interactive>:1:7:
    Couldn't match expected type `a -> String'
           against inferred type `IO String'
    In the second argument of `(.)', namely `getLine'
    In the expression: read . getLine

Тип getLine равен IO String, но read хочет чистый String.

Функция liftM из Control.Monad берет чистую функцию и «поднимает» ее в монаду. Тип выражения многое говорит нам о том, что он делает:

*Main> :type read `liftM` getLine
read `liftM` getLine :: (Read a) => IO a

Это действие ввода-вывода, которое при запуске возвращает нам значение, преобразованное с read, * в нашем случае Int. Напомним, что readLine - это действие ввода-вывода, которое дает значения String, поэтому вы можете думать о liftM как о том, что он позволяет нам применять read «внутри» монады IO.

Пример игры:

1
Too low!
100
Too high!
42
You Win!
8 голосов
/ 29 сентября 2008

Незначительным улучшением оператора case в mattiast (я бы отредактировал, но мне не хватает кармы) является использование функции сравнения, которая возвращает одно из трех значений: LT, GT или EQ:

doGuessing num = do
   putStrLn "Enter your guess:"
   guess <- getLine
   case (read guess) `compare` num of
     LT -> do putStrLn "Too low!"
              doGuessing num
     GT -> do putStrLn "Too high!"
              doGuessing num
     EQ -> putStrLn "You Win!"

Мне очень нравятся эти вопросы на Хаскеле, и я бы посоветовал другим публиковать больше. Часто вы чувствуете, что есть есть , чтобы быть лучшим способом выразить то, что вы думаете, но изначально Haskell настолько чужд, что ничего не приходит на ум.

Бонусный вопрос для журналиста на Haskell: какой тип делания догадок?

8 голосов
/ 25 сентября 2008

Вы можете использовать конструкцию case:

doGuessing num = do
    putStrLn "Enter your guess:"
    guess <- getLine
    case (read guess) of
        g | g < num -> do 
            putStrLn "Too low!"
            doGuessing num
        g | g > num -> do 
            putStrLn "Too high!"
            doGuessing num
        otherwise -> do 
            putStrLn "You Win!"
4 голосов
/ 20 января 2010

То, как Haskell интерпретирует if ... then ... else в блоке do, в значительной степени соответствует всему синтаксису Haskell.

Но многие люди предпочитают немного другой синтаксис, позволяющий then и else появляться на том же уровне отступа, что и соответствующий if. Следовательно, GHC поставляется с расширением языка подписки DoAndIfThenElse, которое разрешает этот синтаксис.

Расширение DoAndIfThenElse стало частью основного языка в последней редакции спецификации Haskell, Haskell 2010 .

3 голосов
/ 12 ноября 2008

Заметьте, что тот факт, что вы должны сделать отступ для блоков then и else внутри блока do, многими считается ошибкой. Вероятно, это будет исправлено в Haskell '(простое число Haskell), следующей версии спецификации Haskell.

1 голос
/ 24 сентября 2008

Вы также можете использовать явную группировку с фигурными скобками. См. Раздел макета http://www.haskell.org/tutorial/patterns.html

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

0 голосов
/ 29 августа 2010

Вы увидите множество различных стилей отступов для Haskell. Большинство из них очень трудно поддерживать без редактора, который настроен на отступ точно в любом стиле.

Стиль, который вы отображаете, намного проще и менее требователен к редактору, и я думаю, что вы должны придерживаться его. Единственное несоответствие, которое я вижу, это то, что вы помещаете первый do в его собственную строку, в то время как вы помещаете другие do после then / else.

Прислушайтесь к другим советам о том, как думать о коде в Haskell, но придерживайтесь своего стиля отступов.

0 голосов
/ 31 октября 2008

Я использую стиль кодирования, как ваш пример из Wikibooks. Конечно, он не следует правилам C, но Haskell - не C, и он довольно читабелен, особенно если вы привыкнете к нему. Он также основан на стиле алгоритмов, используемых во многих учебниках, таких как Кормен.

Добро пожаловать на сайт PullRequest, где вы можете задавать вопросы и получать ответы от других членов сообщества.
...