HaTeXExtension + TH: Спаривание операторов

(репост из tumblr)

Продолжаю серию дополнений HaTeXExtension с использованием Template Haskell. В этом посте будет пример шаблона с рекурсивным сплайсингом/цитированием.

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

1
2
3
a ¸ b = a  comma  b
a  b = a  space  b
a ¸⎕ b = a ¸ ""  b

Хорошо, от первых двух деклараций мы избавились изменив шаблон defTeXCommand. Теперь я хочу сделать шаблон, который будет брать пары операторных символов и генерировать объявление их спаренного варианта. Гм, пошёл делать и понял, что можно брать не пары а списки, чтобы можно было сплавлять сколько угодно операторов.

(…прошло некоторое время…)

В общем я подумал, потыкался, и не придумал, как сделать этот шаблон, чтобы передавать ему сами операторы, потому что непонятно, как имея лишь функцию, получить её имя.. Подумаю ещё потом, а пока сделаю шаблон, который будет брать имена операторов в виде строк и делать своё не гигиеничное спаривание “)

1
2
3
4
5
6
7
8
9
10
defFusedOperators :: [String]   -- ^ strings with operator names
                     Q [Dec]   -- ^ top-level declaration
defFusedOperators list = sequence
    [ concat ops ^= body ops | ops  (map words list) ]
    where
        body, fuse :: [String]  Q Exp
        body ops = [| λ a b  a  $(fuse ops)  b |]

        fuse   []   = [| "" |]
        fuse (x:xs) = [| $(dyn x) "" ""  $(fuse xs) |]

Чтобы не писать [["¸","⎕","≤"], ["¸","…","¸"]], что практически не читабельно, мы будем писать так: ["¸ ⎕ ≤", "¸ … ¸"], а в шаблоне эти строки просто разбиваются по пробелам на списки отдельных имён.

Итак, разберём это определение. Шаблон берёт такой вот список сгруппированных имён операторов, пробегает по нему и создаёт объявления операторов со склеенными именами (concat ops). Тело каждого такого оператора выглядит как лямбда λ a b → a ◇ $(fuse ops) ◇ b, то есть оператор, берёт два аргумента и вклеивает между ними спаренные операторы.

Самое интересное - это fuse. Она берёт список операторов (строк с их именами), и делает рекурсивную подстановку. Напомню, что dyn x - это процитированный оператор, мы его сплайсим, то есть вклеиваем и даём ему два пустых аргумента (у него ведь тип LaTeX → LaTeX → LaTeX), чтобы получить просто значение типа LaTeX. А дальше мы соединяем () это с вклеенным результатом рекурсивного вызова $(fuse xs). Когда же операторы заканчиваются, fuse [] возвращает процитированную пустую строку (на самом деле это значение TeXEmpty).

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

Comments