Szukaj…


Uwagi

Co to jest szablon Haskell?

Szablon Haskell odnosi się do narzędzi do metaprogramowania szablonów wbudowanych w GHC Haskell. Artykuł opisujący oryginalne wdrożenie można znaleźć tutaj .

Jakie są etapy? (Lub jakie jest ograniczenie etapowe?)

Etapy patrz, gdy kod jest wykonywany. Zwykle kod jest wykonywany tylko w czasie wykonywania, ale dzięki szablonowi Haskell kod można wykonać w czasie kompilacji. Kod „normalny” to etap 0, a kod czasu kompilacji to etap 1.

Ograniczenie etapu odnosi się do faktu, że program etapu 0 może nie zostać uruchomiony na etapie 1 - byłoby to równoważne z uruchomieniem dowolnego programu regularnego (nie tylko metaprogramu) w czasie kompilacji.

Zgodnie z konwencją (i dla uproszczenia implementacji) kod w bieżącym module jest zawsze etapem 0, a kod importowany ze wszystkich innych modułów jest etapem 1. Z tego powodu można składać tylko wyrażenia z innych modułów.

Zauważ, że program etapu 1 jest wyrażeniem stopnia 0 typu Q Exp , Q Type itp .; ale odwrotność nie jest prawdą - nie każda wartość (program etapu 0) typu Q Exp jest programem etapu 1,

Ponadto, ponieważ spawy mogą być zagnieżdżane, identyfikatory mogą mieć etapy większe niż 1. Ograniczenie etapu może być następnie uogólnione - program etapu n nie może być wykonany w żadnym etapie m> n . Na przykład w niektórych komunikatach o błędach można zobaczyć odniesienia do takich etapów większych niż 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 |]

Korzystanie z szablonu Haskell powoduje błędy poza zakresem z niepowiązanych identyfikatorów?

Zwykle wszystkie deklaracje w jednym module Haskell można traktować jako wzajemnie rekurencyjne. Innymi słowy, każda deklaracja najwyższego poziomu wchodzi w zakres każdego innego w jednym module. Gdy szablon Haskell jest włączony, zmieniają się zasady określania zakresu - moduł jest zamiast tego dzielony na grupy kodu oddzielone splajnami TH, a każda grupa jest wzajemnie rekurencyjna, a każda grupa jest w zakresie wszystkich dalszych grup.

Typ Q

Konstruktor typu Q :: * -> * zdefiniowany w Language.Haskell.TH.Syntax jest abstrakcyjnym typem reprezentującym obliczenia, które mają dostęp do środowiska czasu kompilacji modułu, w którym wykonywane jest obliczenie. Typ Q obsługuje również podstawianie zmiennych, nazywane przechwytywaniem nazw przez TH (i omówione tutaj .) Wszystkie spawy mają typ QX dla niektórych X

Środowisko czasu kompilacji obejmuje:

  • identyfikatory w zakresie i informacje o tych identyfikatorach,
    • rodzaje funkcji
    • typy i typy danych źródłowych konstruktorów
    • pełna specyfikacja deklaracji typu (klasy, rodziny typów)
  • lokalizacja w kodzie źródłowym (linia, kolumna, moduł, pakiet), w której występuje połączenie
  • poprawności funkcji (GHC 7.10)
  • włączone rozszerzenia GHC (GHC 8.0)

Typ Q ma również możliwość generowania świeżych nazw, z funkcją newName :: String -> Q Name . Zauważ, że nazwa nie jest nigdzie związana, więc użytkownik musi ją sami powiązać, więc upewnienie się, że wynikowe użycie nazwy jest odpowiednio zakrojone, jest obowiązkiem użytkownika.

Q ma instancje dla Functor,Monad,Applicative i jest to główny interfejs do manipulowania wartościami Q , wraz z kombinatorami dostępnymi w Language.Haskell.TH.Lib , które definiują funkcję pomocniczą dla każdego konstruktora TH ast postaci:

LitE :: Lit -> Exp
litE :: Lit -> ExpQ

AppE :: Exp -> Exp -> Exp 
appE :: ExpQ -> ExpQ -> ExpQ

Należy zauważyć, że ExpQ , TypeQ , DecsQ i PatQ są synonimami typów AST, które są zwykle przechowywane w typie Q

Biblioteka TH udostępnia funkcję runQ :: Quasi m => Q a -> ma , i istnieje instancja Quasi IO , więc wydaje się, że typ Q jest tylko fantazyjnym IO . Jednak użycie runQ :: Q a -> IO a powoduje akcję IO która nie ma dostępu do żadnego środowiska kompilacji - jest dostępna tylko w rzeczywistym typie Q Takie działania IO zakończą się niepowodzeniem w czasie wykonywania, jeśli spróbujesz uzyskać dostęp do wspomnianego środowiska.

Curry n-arowe

Znajomy

curry :: ((a,b) -> c) -> a -> b -> c
curry = \f a b -> f (a,b)

funkcję można uogólnić na krotki dowolnego rodzaju, na przykład:

curry3 :: ((a, b, c) -> d) -> a -> b -> c -> d
curry4 :: ((a, b, c, d) -> e) -> a -> b -> c -> d -> e 

Jednak ręczne pisanie takich funkcji dla krotek od 2 do (np.) 20 byłoby żmudne (i ignorowanie faktu, że obecność 20 krotek w twoim programie prawie na pewno sygnalizuje problemy projektowe, które należy naprawić za pomocą rekordów).

Możemy użyć szablonu Haskell do stworzenia takich funkcji curryN dla dowolnego n :

{-# LANGUAGE TemplateHaskell #-}
import Control.Monad (replicateM) 
import Language.Haskell.TH (ExpQ, newName, Exp(..), Pat(..))
import Numeric.Natural (Natural) 

curryN :: Natural -> Q Exp

Funkcja curryN przyjmuje liczbę naturalną i tworzy funkcję curry tego arsenału jako Haskell AST.

curryN n = do
  f  <- newName "f"
  xs <- replicateM (fromIntegral n) (newName "x")

Najpierw tworzymy zmienne typu świeżego dla każdego argumentu funkcji - jeden dla funkcji wejściowej i jeden dla każdego argumentu dla tej funkcji.

  let args = map VarP (f:xs)

Wyrażenie args reprezentuje wzór f x1 x2 .. xn . Zauważ, że wzorzec jest osobną jednostką składniową - moglibyśmy wziąć ten sam wzorzec i umieścić go w lambda, powiązaniu funkcji, a nawet w LHS wiązania let (co byłoby błędem).

      ntup = TupE (map VarE xs)

Funkcja musi zbudować krotkę argumentu z sekwencji argumentów, co właśnie zrobiliśmy tutaj. Zwróć uwagę na różnicę między zmiennymi VarP ( VarP ) a zmiennymi wyrażeniowymi ( VarE ).

  return $ LamE args (AppE (VarE f) ntup)

Wreszcie, wartość, którą produkujemy, to AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

Moglibyśmy również napisać tę funkcję za pomocą cytatów i „podniesionych” konstruktorów:

...
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)) |]

Pamiętaj, że cytaty muszą być poprawne pod względem składniowym, więc [| \ $(map varP (f:xs)) -> .. |] jest niepoprawna, ponieważ w zwykłym Haskell nie ma możliwości zadeklarowania „listy” wzorów - powyższe interpretowane jest jako \ var -> .. i splicowane wyrażenie ma mieć typ PatQ , tj. pojedynczy wzorzec, a nie listę wzorców.

Wreszcie możemy załadować tę funkcję TH w 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

Ten przykład jest dostosowany przede wszystkim stąd .

Składnia szablonu Haskell i quasiquotes

Szablon Haskell jest włączony przez rozszerzenie GHC -XTemplateHaskell . To rozszerzenie włącza wszystkie funkcje syntaktyczne szczegółowo opisane w tej sekcji. Pełne informacje na temat szablonu Haskell podano w instrukcji obsługi .

Splice

  • Splot to nowy obiekt składniowy włączony przez szablon Haskell, zapisany jako $(...) , gdzie (...) jest pewnym wyrażeniem.

  • Pomiędzy $ a pierwszym znakiem wyrażenia nie może być spacji; a Szablon Haskell zastępuje parsowanie operatora $ - np. f$g jest zwykle analizowane jako ($) fg natomiast przy włączonym szablonie Haskell jest analizowane jako połączenie.

  • Gdy złącze pojawi się na najwyższym poziomie, $ może zostać pominięte. W takim przypadku łączone wyrażenie jest całą linią.

  • Splot reprezentuje kod, który jest uruchamiany w czasie kompilacji w celu wytworzenia Haskell AST, i że AST jest kompilowany jako kod Haskell i wstawiany do programu

  • Złączenia mogą pojawiać się zamiast: wyrażeń, wzorów, typów i deklaracji najwyższego poziomu. Typ składanego wyrażenia, odpowiednio, w każdym przypadku to Q Exp , Q Pat , Q Type , Q [Decl] . Pamiętaj, że splice deklaracji mogą pojawiać się tylko na najwyższym poziomie, podczas gdy inne mogą znajdować się odpowiednio w innych wyrażeniach, wzorach lub typach.

Cytaty wyrażeń (uwaga: nie quasi-quuotacja)

  • Cytat wyrażenia jest nowym bytem składniowym zapisanym jako jeden z:

    • [e|..|] lub [|..|] - .. jest wyrażeniem, a cytat ma typ Q Exp ;
    • [p|..|] - .. jest wzorem, a cytat ma typ Q Pat ;
    • [t|..|] - .. jest typem, a cytat ma typ Q Type ;
    • [d|..|] - .. to lista deklaracji, a oferta ma typ Q [Dec] .
  • Cytat z wyrażenia wymaga kompilacji programu czasowego i tworzy wartość AST reprezentowaną przez ten program.

  • Zastosowanie wartości w cudzysłowie (np. \x -> [| x |] ) bez splicingu odpowiada cukierowi syntaktycznemu dla \x -> [| $(lift x) |] , gdzie lift :: Lift t => t -> Q Exp pochodzi z klasy

    class Lift t where
      lift :: t -> Q Exp
      default lift :: Data t => t -> Q Exp

Wpisane splice i cytaty

  • Typowane spawy są podobne do wcześniej wymienionych (nietypowanych) splotów i są zapisywane jako $$(..) gdzie (..) jest wyrażeniem.

  • Jeśli e ma typ Q (TExp a) to $$e ma typ a .

  • Cytowane typy mają postać [||..||] gdzie .. jest wyrażeniem typu a ; wynikowy cytat ma typ Q (TExp a) .

  • Wpisane wyrażenie można przekonwertować na te bez typu: unType :: TExp a -> Exp .

QuasiQuotes

  • QuasiQuotes uogólniają cytaty wyrażeń - wcześniej parser używany przez cytat wyrażenia był jednym z ustalonego zestawu ( e,p,t,d ), ale QuasiQuotes pozwala na zdefiniowanie niestandardowego analizatora składni i użycie go do wygenerowania kodu w czasie kompilacji. Quasi-cytaty mogą pojawiać się w tych samych kontekstach, co zwykłe cytaty.

  • Quasi-cytat zapisywany jest jako [iden|...|] , gdzie iden jest identyfikatorem typu Language.Haskell.TH.Quote.QuasiQuoter .

  • QuasiQuoter składa się po prostu z czterech parserów, po jednym dla każdego z różnych kontekstów, w których mogą pojawiać się cytaty:

    data QuasiQuoter = QuasiQuoter { quoteExp  :: String -> Q Exp,
                                     quotePat  :: String -> Q Pat,
                                     quoteType :: String -> Q Type,
                                     quoteDec  :: String -> Q [Dec] }

Nazwy

  • Identyfikatory Haskell są reprezentowane przez typ Language.Haskell.TH.Syntax.Name . Nazwy tworzą liście abstrakcyjnych drzew składniowych reprezentujących programy Haskell w szablonie Haskell.

  • Identyfikator, który jest obecnie objęty zakresem, może zostać przekształcony w nazwę z: 'e 'T lub 'T . W pierwszym przypadku e jest interpretowane w zakresie wyrażenia, podczas gdy w drugim przypadku T jest w zakresie typu (przypominając, że konstruktory typów i wartości mogą dzielić nazwę bez dwuznaczności w Haskell).



Modified text is an extract of the original Stack Overflow Documentation
Licencjonowany na podstawie CC BY-SA 3.0
Nie związany z Stack Overflow