Поиск…


замечания

Что такое шаблон 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).



Modified text is an extract of the original Stack Overflow Documentation
Лицензировано согласно CC BY-SA 3.0
Не связан с Stack Overflow