Как мне справиться со многими уровнями отступов?

Я пишу скрипт с очень сложным логическим циклом:

main = do
    inFH <- openFile "..." ReadMode
    outFH <- openFile "..." WriteMode

    forM myList $ \ item ->
        ...
        if ... 
            then ...
            else do
                ...
                case ... of
                    Nothing -> ...
                    Just x  -> do
                        ...
                            ...

Код вскоре летит вправо, поэтому я подумал разбить его на части, используя, например, пункты where. Проблема в том, что многие из этих ... содержат операторы чтения/записи для двух дескрипторов inFH и outFH, и использование оператора where сделает эти два имени вне контекста. Мне пришлось бы отправлять эти две переменные каждый раз, когда я использую оператор where.

Есть ли лучший способ справиться с этим?


person xzhu    schedule 08.10.2015    source источник


Ответы (3)


Во многих случаях эти глубоко вложенные отступы являются результатом глубоко вложенной проверки ошибок. Если это так для вас, вы должны изучить MaybeT и его старшего брата ExceptT. Они предлагают простой способ отделить код «что делать, если что-то пошло не так» от кода «что делать, если все идет как надо». В вашем примере я мог бы написать:

data CustomError = IfCheckFailed | MaybeCheckFailed

main = handleErrors <=< runExceptT $ do
    inFH  <- liftIO $ openFile ...
    outFH <- liftIO $ openFile ...
    forM myList $ \item -> do
        when (...) (throwError IfCheckFailed)
        ...
        x <- liftMaybe MaybeCheckFailed ...
        ...

liftMaybe :: MonadError e m => e -> Maybe a -> m a
liftMaybe err = maybe (throwError err) return

handleErrors :: Either CustomError a -> IO a
handleErrors (Left err) = case err of
    IfCheckFailed    -> ...
    MaybeCheckFailed -> ...
handleErrors (Right success) = return success

Обратите внимание, что мы по-прежнему увеличиваем отступ в цикле forM; но другие проверки выполняются «в строке» в main и обрабатываются на одном уровне отступа в handleErrors.

person Daniel Wagner    schedule 08.10.2015

Хотя, вероятно, есть более удобные способы решения вашей конкретной проблемы (см., например, ответ Даниэля Вагнера), вы всегда можете использовать let для введения нового имени в произвольной области. Вот, по общему признанию, бессмысленная демонстрация:

main = do
    inFH <- return "inf"
    outFH <- return "ouf"

    let subAction = do
            if length inFH > 2
                then print "foo"
                else subSubAction

        subSubAction = case outFH of
            [] -> print "bar"
            _ -> print "baz"

    forM [1..10] $ \ item -> do
        print item
        subAction
person duplode    schedule 08.10.2015
comment
Спасибо. Это то, что я проглядел. Однако одна вещь, которая мне не нравится в использовании здесь let, заключается в том, что он меняет порядок чтения — сначала я определяю часть шага, а затем идет остальная часть потока. Мне также не нравится давать части логического потока имя, что является еще одной причиной, по которой я хотел избежать использования where. - person xzhu; 09.10.2015

Вы должны сделать то же самое, что и с любым другим языком программирования. Функции должны быть понятными. Обычно это означает, что если он длинный, то поток управления невелик, в противном случае разделите его на отдельные функции.

Таким образом, main может выглядеть так:

main = do
    inFH <- openFile ...
    outFH <- openFile ....

    mapM prcoessItem myList
person dave    schedule 08.10.2015
comment
Гм, это в значительной степени несовместимо с заявленным требованием не передавать везде inFH и outFH. - person Ørjan Johansen; 08.10.2015