понедельник, 15 сентября 2014 г.

Вью-паттерны (view patterns) в haskell на конкретном примере

Как перевести на русский язык view patterns я, честно говоря, не знаю. Образцы с представлением? Образцы с функциональным преобразованием? Чтобы случайно не исказить смысл этих объектов, я буду называть их просто вью-паттернами. Хотя смысл, в общем-то, простой: части общего образца заменяются вызовами функций, которые возвращают объекты произвольного типа — эти объекты представлены в виде образцов, элементы которых, как обычно, связываются с переменными в теле основной функции. Такие функции называются view, а образцы, на которые они указывают (с помощью стрелочки ->) — view patterns. Подробнее о вью-паттернах можно узнать здесь. Вот конкретный пример. Возьмем многострадальный фильтр для pandoc. В нем есть (или уже был, если я решу закоммитить новое решение) участок
substInlineStyle :: Format -> MMap -> Inline -> Inline
substInlineStyle fm m i@(Image ((Style style):alt) (src, title)) =
    substInlineStyle' fm m style (alt, src, title) i
substInlineStyle fm m i@(Link ((Style style):alt) (src, title)) =
    substInlineStyle' fm m style (alt, src, title) i
substInlineStyle _ _ i = i

substInlineStyle' :: Format -> MMap -> String -> ObjParams -> Inline -> Inline
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
Что мне здесь не нравится? Первый и второй клозы функции substInlineStyle отличаются только именем конструктора в образце (Image и Link), больше эти имена нигде не фигурируют. Оба конструктора имеют одинаковые аргументы [Inline] Target (так они определены в pandoc). Так почему бы не избавиться от этих конструкторов таким образом, чтобы объединить первый и второй клозы substInlineStyle в одну функцию? Однако, плейсхолдеры конструкторов в образцах в haskell не используются, охранные выражения с образцами (см. предыдущую статью) здесь тоже бесполезны (они всё так же не смогут убрать имена конструкторов или заменить их плейсхолдерами). А чем здесь могут помочь вью-паттерны? Ну, они могут заменить тип Inline с его конструкторами Image и Link на какой-нибудь новый тип, например, кортеж из двух аргументов этих конструкторов, обернутый в Maybe!
substInlineStyle :: Format -> MMap -> Inline -> Inline
substInlineStyle (Format fm) m
                 i@(getInlineParams -> Just (((Style style):alt), (src, title)))
    | Just (MetaMap mm) <- M.lookup style m
    , Just (MetaBlocks [Para ((RawInline f s):r)]) <- M.lookup fm mm =
        let params = (alt, src, title)
            subst (Style "ALT") = RawInline f "$ALT$"
            subst i = i
        in RawInline f $ substParams fm params $
                                s ++ stringify' fm (map subst r)
    | otherwise = i
substInlineStyle _ _ i = i

getInlineParams :: Inline -> Maybe ([Inline], Target)
getInlineParams (Image alt target) = Just (alt, target)
getInlineParams (Link alt target) = Just (alt, target)
getInlineParams _ = Nothing
Еще один вариант — искать стиль сразу в getInlineParams и возвращать из нее кортеж из трех элементов — стиля, списка alt и элемента target.
substInlineStyle :: Format -> MMap -> Inline -> Inline
substInlineStyle (Format fm) m
                 i@(getInlineParams -> Just (Style style, alt, (src, title)))
    | Just (MetaMap mm) <- M.lookup style m
    , Just (MetaBlocks [Para ((RawInline f s):r)]) <- M.lookup fm mm =
        let params = (alt, src, title)
            subst (Style "ALT") = RawInline f "$ALT$"
            subst i = i
        in RawInline f $ substParams fm params $
                                s ++ stringify' fm (map subst r)
    | otherwise = i
substInlineStyle _ _ i = i

getInlineParams :: Inline -> Maybe (Inline, [Inline], Target)
getInlineParams (Image (style@(Style _):alt) target) = Just (style, alt, target)
getInlineParams (Link (style@(Style _):alt) target) = Just (style, alt, target)
getInlineParams _ = Nothing
Вот и всё! Нам удалось выбросить имена Image и Link из substInlineStyle и благодаря этому объединить два ее клоза в один. Правда, пришлось добавить новую вью-функцию getInlineParams, но в любом случае возможности и семантика нового решения стали намного богаче. Во-первых, вью-функцию можно спрятать в библиотеку (или же, в общем случае, эта функция может быть предоставлена библиотекой, либо являться стандартной), во-вторых, вью-функция может производить важные семантические изменения данных, а не просто выдергивать данные из конструкторов, как в нашем случае. Вот просто потрясающий пример, иллюстрирующий второе утверждение (найден здесь):
endpoints (sort -> begin : (reverse -> end : _)) = Just (begin, end)
endpoints _ = Nothing
Эта штука находит минимальный и максимальный элементы в произвольном списке, предварительно сортируя этот список прямо в образце! А представьте что будет, если это скрестить с синонимами образцов!
pattern Sort begin end <- (sort -> begin : (reverse -> end : _))

endpoints (Sort begin end) = Just (begin, end)
endpoints _ = Nothing

min (Sort begin _) = Just begin
min _ = Nothing

max (Sort _ end) = Just end
max _ = Nothing
Сплошное очарование. Обратите внимание: несмотря на то, что функции endpoints, min и max ожидают на входе список, переменные их образцов связаны с конкретными элементами списка — концами begin и end. Осталось сказать, что поскольку вью-паттерны являются нестандартным расширением, то для их использования в начало программы следует добавить прагму
{-# LANGUAGE ViewPatterns #-}
, либо при компиляции указать опцию -XViewPatterns.

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

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