пятница, 12 сентября 2014 г.

Охранные выражения с образцом (pattern guards) в haskell на конкретном примере

Я опять переписал фильтр для pandoc, о котором рассказывал здесь и здесь. Давайте посмотрим на оригинальное определение первого клоза функции substStyle.
substStyle (Format fm) m b@(Para [Image ((Style style):alt) (src, title)]) =
    case M.lookup style m of
        Nothing -> b
        Just (MetaMap mm) ->
            let params = (alt, src, title)
            in case M.lookup fm mm of
                   Nothing -> b
                   Just (MetaBlocks [RawBlock f s]) ->
                       RawBlock f $ substParams fm params s
                   Just (MetaBlocks [mb]) ->
                       walk substParams' mb
                       where substParams' (RawInline f s) =
                                   RawInline f $ substParams fm params s
                             substParams' i = i
                   Just _ -> b
        Just _ -> b
Смысл этой функции простой: вернуть новое форматирование в случае, если имя style было найдено в списке объектов метаданных документа (результат Just (MetaMap mm) в первом выражении case) и представление найденного объекта метаданных соответствует MetaBlocks [RawBlock f s] (для встроенного кода latex) или просто MetaBlocks [mb] (для встроенного кода html) как результат проверки второго case. Несмотря на то, что нас фактически интересуют только эти два условия (в остальных случаях мы просто вернем исходный блок b), использование вложенных case привело к формированию неоправданно разросшегося куста с шестью ветвями, четыре из которых в общем-то ничего полезного не делают (просто возвращают b). Как бы нам подрезать ненужные ветви? Оказывается, в haskell есть механизм, который позволит решить эту задачу. Это охранные выражения с образцом (я попытался перевести pattern guards без искажения семантики этих объектов). Документация доступна здесь. Несмотря на то, что охранные выражения с образцом являются синтаксическим расширением, никакие дополнительные флаги компиляции для их использования не требуются, поскольку они были включены в стандарт Haskell 2010. В упомянутой выше документации приводится пример с двумя последовательными поисками в отображении: как раз то, что нам нужно. Вот результат трансляции кейсов из substStyle в охранные выражения.
substStyle (Format fm) m b@(Para [Image ((Style style):alt) (src, title)])
    | Just (MetaMap mm) <- M.lookup style m
    , Just (MetaBlocks [mb]) <- M.lookup fm mm =
        let params = (alt, src, title)
            substStyle' mb
                | RawBlock f s <- mb =
                    RawBlock f $ substParams fm params s
                | otherwise =
                    let substParams' (RawInline f s) =
                                RawInline f $ substParams fm params s
                        substParams' i = i
                    in walk substParams' mb
        in substStyle' mb
    | otherwise = b
Итак, новая функция substStyle реализована с помощью охранных выражений с образцом, в которых последовательно ищутся значение style в метаданных документа и (на основе уже найденного объекта-стиля mm!) представление стиля в метаданных. Если представление стиля соответствует MetaBlocks [mb], то, с помощью функции substStyle’, которая тоже реализована с помощью охранных выражений с образцом, выполняется подстановка шаблона стиля в документ. Хочу также отметить, что в данном случае let-выражения нельзя заменить словом where, поскольку последнее может быть использовано только после перечисления всех охранных выражений функции, таким образом замена внешнего let на where приведет просто к ошибке компиляции, ну а в случае с внутренним let его замена на where слегка изменит семантику: программа в общем-то скомпилируется, поскольку where будет стоять после всех охранных выражений, однако все объявления внутри него (то есть функция substParams’) будут видны во всех ветвях охранных выражений — а зачем нам это надо? Оставшиеся трансляции case в pattern guards. Из
substStyle (Format fm) m b@(Para cnt) =
    let walk' = walk $ substInlineStyle (Format fm) m
    in case M.lookup "para_style" m of
           Nothing -> walk' b
           Just (MetaMap mm) ->
               case M.lookup fm mm of
                   Nothing -> walk' b
                   Just (MetaBlocks [Para [Span attr _]]) ->
                       walk' $ Plain [Span attr cnt]
                   Just _ -> walk' b
           Just _ -> walk' b
в
substStyle (Format fm) m b@(Para cnt)
    | Just (MetaMap mm) <- M.lookup "para_style" m
    , Just (MetaBlocks [Para [Span attr _]]) <- M.lookup fm mm =
        walk' $ Plain [Span attr cnt]
    | otherwise = walk' b
    where walk' = walk $ substInlineStyle (Format fm) m
(здесь мы использовали where, поскольку объявленная в нем функция walk’ должна быть доступна во всех ветвях охранных выражений). Из
substInlineStyle' (Format fm) m style params i =
    case M.lookup style m of
        Nothing -> i
        Just (MetaMap mm) ->
            case M.lookup fm mm of
                Nothing -> i
                Just (MetaBlocks [Para ((RawInline f s):r)]) ->
                    RawInline f $ substParams fm params $
                                    s ++ stringify' fm (map subst r)
                    where subst (Style "ALT") = RawInline f "$ALT$"
                          subst i = i
                Just _ -> i
        Just _ -> i
в
substInlineStyle' (Format fm) m style params i
    | Just (MetaMap mm) <- M.lookup style m
    , Just (MetaBlocks [Para ((RawInline f s):r)]) <- M.lookup fm mm =
        let subst (Style "ALT") = RawInline f "$ALT$"
            subst i = i
        in RawInline f $ substParams fm params $
                                s ++ stringify' fm (map subst r)
    | otherwise = i
Итак, мы заменили все case на охранные выражения, сократили код на 12 строк и сделали его более понятным. По-моему, это вин! Update. Реализовав локальную функцию substStyle’ в первом клозе substStyle с помощью охранных выражений, я, честно говоря, перестарался. В самом деле, какой смысл вытаскивать образец из связанной переменной, если эту переменную можно изначально представить в виде декомпозиции с интересующим нас конструктором? Использование охранных выражений с образцом в этом случае — это просто ненатуральный аналог сопоставления с образцом. Таким образом, первый клоз substStyle следует переписать так:
substStyle (Format fm) m b@(Para [Image ((Style style):alt) (src, title)])
    | Just (MetaMap mm) <- M.lookup style m
    , Just (MetaBlocks [mb]) <- M.lookup fm mm =
        let params = (alt, src, title)
            substStyle' (RawBlock f s) = RawBlock f $ substParams fm params s
            substStyle' b = walk substParams' b
                where substParams' (RawInline f s) =
                            RawInline f $ substParams fm params s
                      substParams' i = i
        in substStyle' mb
    | otherwise = b
И substParams’ теперь можно определить внутри блока where, поскольку два определения substStyle’ стали лексически независимы, в отличие от случая с охранными выражениями.

Комментариев нет:

Отправить комментарий