Haskell Language
Шаблон Haskell & QuasiQuotes
Поиск…
замечания
Что такое шаблон Haskell?
Шаблон Haskell ссылается на объекты мета-программирования шаблонов, встроенные в GHC Haskell. Статья, описывающая первоначальную реализацию, можно найти здесь .
Что такое этапы? (Или, что такое ограничение на сцену?)
Этапы относятся к тому, когда выполняется код. Обычно код вызывается только во время выполнения, но с шаблоном Haskell код может быть выполнен во время компиляции. «Нормальный» код - это этап 0, а код компиляции - этап 1.
Сценарное ограничение относится к тому, что на этапе 1 программа этапа 0 не может быть выполнена - это было бы эквивалентно возможности запуска любой обычной программы (а не только метапрограммы) во время компиляции.
По соглашению (и ради простоты реализации) код в текущем модуле всегда является этапом 0, а код, импортированный из всех других модулей, является этапом 1. По этой причине могут быть объединены только выражения из других модулей.
Обратите внимание, что программа 1-го этапа представляет собой выражение этапа 0 типа Q Exp
, Q Type
и т. Д .; но обратное неверно - не каждое значение (программа этапа 0) типа Q Exp
является программой этапа 1,
Более того, поскольку сращивания могут быть вложенными, идентификаторы могут иметь этапы, превышающие 1. Тогда может быть обобщено ограничение на сцену - программа этапа n может не выполняться ни на одной стадии m> n . Например, в некоторых сообщениях об ошибках можно увидеть ссылки на такие этапы, которые превышают 1:
>:t [| \x -> $x |]
<interactive>:1:10: error:
* Stage error: `x' is bound at stage 2 but used at stage 1
* In the untyped splice: $x
In the Template Haskell quotation [| \ x -> $x |]
Использование шаблона Haskell приводит к ошибкам не в области видимости от несвязанных идентификаторов?
Обычно все объявления в одном модуле Haskell можно рассматривать как все взаимно-рекурсивные. Другими словами, каждое объявление верхнего уровня входит в объем каждого другого в одном модуле. Когда Template Haskell включен, правила определения области действия изменяются - модуль вместо этого разбивается на группы кода, разделенные с помощью сплайнов TH, и каждая группа является взаимно рекурсивной, и каждая группа охватывает все остальные группы.
Тип Q
Конструктор типа Q :: * -> *
определенный в Language.Haskell.TH.Syntax
- абстрактный тип, представляющий вычисления, которые имеют доступ к среде времени компиляции модуля, в котором выполняется вычисление. Тип Q
также обрабатывает переменную подстановку, называемую захватом имени TH (и обсуждаемой здесь ). Все сращивания имеют тип QX
для некоторого X
Среда компиляции включает в себя:
- идентификаторы в области видимости и информацию об упомянутых идентификаторах,
- типы функций
- типы и исходные типы данных конструкторов
- полная спецификация деклараций типов (классы, типы семейств)
- расположение в исходном коде (строка, столбец, модуль, пакет), где происходит сращивание
- исправления функций (GHC 7.10)
- включенные расширения GHC (GHC 8.0)
Тип Q
также имеет возможность генерировать новые имена, с функцией newName :: String -> Q Name
. Обратите внимание, что имя не привязано нигде неявно, поэтому пользователь должен привязать его самостоятельно, и поэтому убедитесь, что полученное использование имени хорошо охвачено пользователем.
Q
имеет экземпляры для Functor,Monad,Applicative
и это основной интерфейс для манипулирования значениями Q
вместе с комбинаторами, предоставляемыми в Language.Haskell.TH.Lib
, которые определяют вспомогательную функцию для каждого конструктора TH ast формы:
LitE :: Lit -> Exp
litE :: Lit -> ExpQ
AppE :: Exp -> Exp -> Exp
appE :: ExpQ -> ExpQ -> ExpQ
Обратите внимание, что ExpQ
, TypeQ
, DecsQ
и PatQ
являются синонимами для типов AST, которые обычно хранятся внутри Q
типа.
Библиотека TH предоставляет функцию runQ :: Quasi m => Q a -> ma
, и есть экземпляр Quasi IO
, поэтому кажется, что тип Q
- просто причудливый IO
. Однако использование runQ :: Q a -> IO a
вызывает действие IO
которое не имеет доступа к какой-либо среде компиляции - это доступно только в реальном Q
типе. Такие действия IO
будут работать во время выполнения, если вы попытаетесь получить доступ к указанной среде.
Карьера n-arity
Знакомый
curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)
функция может быть обобщена на кортежи произвольной арности, например:
curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e
Однако писать такие функции для кортежей arity 2 (например, 20) вручную было бы утомительно (и игнорируя тот факт, что наличие 20 кортежей в вашей программе почти наверняка сигнализирует о проблемах дизайна, которые должны быть исправлены с помощью записей).
Мы можем использовать Template Haskell для создания таких функций curryN
для произвольного n
:
{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM)
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural)
curryN :: Natural -> Q Exp
Функция curryN
принимает натуральное число и производит функцию карри этой арности, как Haskell AST.
curryN n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
Сначала мы производим новые переменные типа для каждого из аргументов функции - один для входной функции и по одному для каждого из аргументов указанной функции.
let args = map VarP (f:xs)
Выражение args
представляет собой шаблон f x1 x2 .. xn
. Обратите внимание, что шаблон представляет собой отдельный синтаксический объект - мы могли бы взять этот же шаблон и поместить его в лямбда или привязку функции или даже LHS привязки let (что было бы ошибкой).
ntup = TupE (map VarE xs)
Функция должна строить кортеж аргумента из последовательности аргументов, что мы и сделали здесь. Обратите внимание на различие между переменными шаблона ( VarP
) и переменными выражения ( VarE
).
return $ LamE args (AppE (VarE f) ntup)
Наконец, значение, которое мы производим, это AST \f x1 x2 .. xn -> f (x1, x2, .. , xn)
.
Мы могли бы также написать эту функцию, используя цитаты и «поднятые» конструкторы:
...
import Language.Haskell.TH.Lib
curryN' :: Natural -> ExpQ
curryN' n = do
f <- newName "f"
xs <- replicateM (fromIntegral n) (newName "x")
lamE (map varP (f:xs))
[| $(varE f) $(tupE (map varE xs)) |]
Обратите внимание, что цитаты должны быть синтаксически действительными, поэтому [| \ $(map varP (f:xs)) -> .. |]
недействителен, потому что в регулярном Haskell нет способа объявить «список» шаблонов - вышеупомянутое интерпретируется как \ var -> ..
и ожидается, что сплайсированное выражение будет иметь тип PatQ
, т. е. один шаблон, а не список шаблонов.
Наконец, мы можем загрузить эту функцию TH в GHCi:
>:set -XTemplateHaskell
>:t $(curryN 5)
$(curryN 5)
:: ((t1, t2, t3, t4, t5) -> t) -> t1 -> t2 -> t3 -> t4 -> t5 -> t
>$(curryN 5) (\(a,b,c,d,e) -> a+b+c+d+e) 1 2 3 4 5
15
Этот пример адаптирован в основном отсюда .
Синтаксис шаблона Haskell и Quasiquotes
Шаблон Haskell включен расширением -XTemplateHaskell
GHC. Это расширение позволяет использовать все синтаксические функции в этом разделе. Подробные сведения о шаблоне Haskell приведены в руководстве пользователя .
Сращивания
Сплайсинг - это новый синтаксический объект, разрешенный Template Haskell, написанный как
$(...)
, где(...)
- некоторое выражение.Между
$
и первым символом выражения не должно быть пробела; и Template Haskell переопределяет синтаксический анализ оператора$
- например,f$g
обычно анализируется как($) fg
тогда как с включенным Template Haskell он анализируется как сплайсинг.Когда сращивание появляется на верхнем уровне, значение
$
может быть опущено. В этом случае сплайсированное выражение представляет собой всю строку.Сплайсинг представляет собой код, который запускается во время компиляции, чтобы получить Haskell AST, и что AST составлен как код Haskell и вставлен в программу
Сплавы могут появляться вместо выражений, шаблонов, типов и объявлений верхнего уровня. Тип сращиваемого выражения в каждом случае, соответственно,
Q Exp
,Q Pat
,Q Type
,Q [Decl]
. Обратите внимание, что сплайты объявления могут отображаться только на верхнем уровне, тогда как другие могут быть внутри других выражений, шаблонов или типов, соответственно.
Котировки выражения (примечание: не QuasiQuotation)
Котировка выражения - это новый синтаксический объект, написанный как один из следующих:
-
[e|..|]
или[|..|]
-..
является выражением, а котировка имеет типQ Exp
; -
[p|..|]
-..
является шаблоном, а котировка имеет типQ Pat
; -
[t|..|]
-..
является типом, а предложение имеет типQ Type
; -
[d|..|]
-..
является списком деклараций, а цитата имеет типQ [Dec]
.
-
Котировка выражения принимает программу времени компиляции и выдает АСТ, представленную этой программой.
Использование значения в цитате (например,
\x -> [| x |]
) без сращивания соответствует синтаксическому сахару для\x -> [| $(lift x) |]
, гдеlift :: Lift t => t -> Q Exp
исходит из класса
class Lift t where lift :: t -> Q Exp default lift :: Data t => t -> Q Exp
Типизированные сращивания и котировки
Типизированные сращивания аналогичны ранее упомянутым (нетипизированным) сращиваниям и записываются как
$$(..)
где(..)
- выражение.Если
e
имеет типQ (TExp a)
то$$e
имеет типa
.Типизированные цитаты принимают вид
[||..||]
где..
- выражение типаa
; результирующая котировка имеет типQ (TExp a)
.Типированное выражение может быть преобразовано в нетипизированные:
unType :: TExp a -> Exp
.
QuasiQuotes
QuasiQuotes обобщает цитаты с выражениями - ранее, синтаксический анализатор, используемый котировкой выражения, является одним из фиксированных множеств (
e,p,t,d
), но QuasiQuotes позволяет определить пользовательский парсер и использовать его для создания кода во время компиляции. Квази-котировки могут появляться во всех тех же контекстах, что и обычные котировки.[iden|...|]
как[iden|...|]
, гдеiden
является идентификатором типаLanguage.Haskell.TH.Quote.QuasiQuoter
.QuasiQuoter
просто состоит из четырех парсеров, по одному для каждого из разных контекстов, в которых могут появляться цитаты:
data QuasiQuoter = QuasiQuoter { quoteExp :: String -> Q Exp, quotePat :: String -> Q Pat, quoteType :: String -> Q Type, quoteDec :: String -> Q [Dec] }
имена
Идентификаторы Haskell представлены типом
Language.Haskell.TH.Syntax.Name
. Имена образуют листья абстрактных деревьев синтаксиса, представляющие программы Haskell в Template Haskell.Идентификатор, который в настоящее время находится в области видимости, может быть превращен в имя с именем:
'e
или'T
В первом случаеe
интерпретируется в области выражения, тогда как во втором случаеT
находится в области типов (напомним, что типы конструкторов типов и значений могут совместно использовать имя без каких-либо ошибок в Haskell).