Buscar..


Observaciones

¿Qué es la plantilla Haskell?

La plantilla Haskell se refiere a las instalaciones de meta-programación de plantillas incorporadas en GHC Haskell. El documento que describe la implementación original se puede encontrar aquí .

¿Qué son las etapas? (O, ¿cuál es la restricción de la etapa?)

Las etapas se refieren a cuando se ejecuta el código. Normalmente, el código se ejerce solo en tiempo de ejecución, pero con Template Haskell, el código puede ejecutarse en tiempo de compilación. El código "normal" es la etapa 0 y el código de tiempo de compilación es la etapa 1.

La restricción de etapa se refiere al hecho de que un programa de etapa 0 no puede ejecutarse en la etapa 1; esto sería equivalente a poder ejecutar cualquier programa regular (no solo un meta-programa) en el momento de la compilación.

Por convención (y para simplificar la implementación), el código en el módulo actual siempre es la etapa 0 y el código importado de todos los demás módulos es la etapa 1. Por esta razón, solo las expresiones de otros módulos pueden empalmarse.

Tenga en cuenta que un programa de etapa 1 es una expresión de etapa 0 de tipo Q Exp , Q Type , etc .; pero lo contrario no es cierto: no todos los valores (programa de la etapa 0) de tipo Q Exp son programas de la etapa 1,

Además, como los empalmes se pueden anidar, los identificadores pueden tener etapas superiores a 1. La restricción de etapa puede generalizarse: un programa de etapa n no puede ejecutarse en ninguna etapa m> n . Por ejemplo, uno puede ver referencias a tales etapas mayores que 1 en ciertos mensajes de error:

>: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 |]

¿El uso de Template Haskell causa errores no relacionados con el alcance de identificadores no relacionados?

Normalmente, todas las declaraciones en un solo módulo de Haskell se pueden considerar como todas recursivas mutuamente. En otras palabras, cada declaración de nivel superior está dentro del alcance de todas las demás en un solo módulo. Cuando Template Haskell está habilitado, las reglas de alcance cambian: el módulo se divide en grupos de código separados por empalmes TH, y cada grupo es recursivo mutuamente, y cada grupo está dentro del alcance de todos los grupos adicionales.

El tipo q

El constructor de tipo Q :: * -> * definido en Language.Haskell.TH.Syntax es un tipo abstracto que representa los cálculos que tienen acceso al entorno de tiempo de compilación del módulo en el que se ejecuta el cálculo. El tipo Q también maneja la sustitución de variables, llamada captura de nombre por TH (y se explica aquí ). Todos los empalmes tienen el tipo QX para algunas X

El entorno de tiempo de compilación incluye:

  • identificadores en el ámbito e información sobre dichos identificadores,
    • tipos de funciones
    • tipos y fuentes de datos tipos de constructores
    • Especificación completa de las declaraciones de tipo (clases, familias de tipo)
  • la ubicación en el código fuente (línea, columna, módulo, paquete) donde se produce el empalme
  • Fijidades de funciones (GHC 7.10)
  • extensiones GHC habilitadas (GHC 8.0)

El tipo Q también tiene la capacidad de generar nombres nuevos, con la función newName :: String -> Q Name . Tenga en cuenta que el nombre no está vinculado en ningún lugar de forma implícita, por lo que el usuario debe vincularlo por sí mismo, por lo que asegurarse de que el uso resultante del nombre esté bien incluido es responsabilidad del usuario.

Q tiene instancias para Functor,Monad,Applicative y esta es la interfaz principal para manipular los valores de Q , junto con los combinadores proporcionados en Language.Haskell.TH.Lib , que definen una función auxiliar para cada constructor de la forma TH:

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

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

Tenga en cuenta que ExpQ , TypeQ , DecsQ y PatQ son sinónimos de los tipos de AST que normalmente se almacenan dentro del tipo Q

La biblioteca TH proporciona una función runQ :: Quasi m => Q a -> ma , y hay una instancia de Quasi IO , por lo que parece que el tipo Q es solo una IO elegante. Sin embargo, el uso de runQ :: Q a -> IO a produce una acción de IO que no tiene acceso a ningún entorno de tiempo de compilación; solo está disponible en el tipo de Q real. Tales acciones de IO fallarán en el tiempo de ejecución si se intenta acceder a dicho entorno.

Un curry n-arity

Lo familiar

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

La función puede generalizarse a tuplas de aridad arbitraria, por ejemplo:

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

Sin embargo, escribir tales funciones para tuplas de aridad 2 a (p. Ej., 20 a mano) sería tedioso (e ignorar el hecho de que la presencia de 20 tuplas en su programa casi con toda seguridad indica problemas de diseño que deberían solucionarse con los registros).

Podemos usar Template Haskell para producir tales funciones curryN para n arbitrario:

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

curryN :: Natural -> Q Exp

La función curryN toma un número natural y produce la función curry de esa aridad, como un AST de Haskell.

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

Primero producimos nuevas variables de tipo para cada uno de los argumentos de la función, uno para la función de entrada y uno para cada uno de los argumentos de dicha función.

  let args = map VarP (f:xs)

La expresión args representa el patrón f x1 x2 .. xn . Tenga en cuenta que un patrón es una entidad sintáctica separada: podríamos tomar este mismo patrón y colocarlo en un lambda, o una función de enlace, o incluso el LHS de un enlace de dejar (que sería un error).

      ntup = TupE (map VarE xs)

La función debe construir la tupla de argumentos a partir de la secuencia de argumentos, que es lo que hemos hecho aquí. Observe la distinción entre variables de patrón ( VarP ) y variables de expresión ( VarE ).

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

Finalmente, el valor que producimos es el AST \f x1 x2 .. xn -> f (x1, x2, .. , xn) .

También podríamos haber escrito esta función usando citas y constructores 'levantados':

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

Tenga en cuenta que las citas deben ser sintácticamente válidas, por lo que [| \ $(map varP (f:xs)) -> .. |] no es válido, porque en Haskell normal no hay forma de declarar una 'lista' de patrones; lo anterior se interpreta como \ var -> .. y se espera que la expresión empalmada tenga el tipo PatQ , es decir, un solo patrón, no una lista de patrones.

Finalmente, podemos cargar esta función TH en 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

Este ejemplo está adaptado principalmente desde aquí .

Sintaxis de plantilla Haskell y cuasiquotes

La plantilla Haskell está habilitada por la extensión -XTemplateHaskell GHC. Esta extensión habilita todas las características sintácticas más detalladas en esta sección. Los detalles completos sobre Template Haskell están dados por la guía del usuario .

Empalmes

  • Un empalme es una nueva entidad sintáctica habilitada por Template Haskell, escrita como $(...) , donde (...) es una expresión.

  • No debe haber un espacio entre $ y el primer carácter de la expresión; y Template Haskell anula el análisis del operador $ , por ejemplo, f$g normalmente se analiza como ($) fg mientras que con la plantilla Haskell habilitada, se analiza como un empalme.

  • Cuando aparece un empalme en el nivel superior, se puede omitir el $ . En este caso, la expresión empalmada es la línea completa.

  • Un empalme representa el código que se ejecuta en tiempo de compilación para producir un AST de Haskell, y ese AST se compila como un código de Haskell y se inserta en el programa

  • Los empalmes pueden aparecer en lugar de: expresiones, patrones, tipos y declaraciones de nivel superior. El tipo de expresión empalmada, en cada caso respectivamente, es Q Exp , Q Pat , Q Type , Q [Decl] . Tenga en cuenta que los empalmes de declaración solo pueden aparecer en el nivel superior, mientras que los otros pueden estar dentro de otras expresiones, patrones o tipos, respectivamente.

Citas de expresión (nota: no una cotización)

  • Una cita de expresión es una nueva entidad sintáctica escrita como una de:

    • [e|..|] o [|..|] - .. es una expresión y la cita tiene el tipo Q Exp ;
    • [p|..|] - .. es un patrón y la cita tiene el tipo Q Pat ;
    • [t|..|] - .. es un tipo y la cita tiene el tipo Q Type ;
    • [d|..|] - .. es una lista de declaraciones y la cita tiene el tipo Q [Dec] .
  • Una cita de expresión toma un programa de tiempo de compilación y produce el AST representado por ese programa.

  • El uso de un valor en una cita (por ejemplo, \x -> [| x |] ) sin un empalme corresponde al azúcar sintáctico para \x -> [| $(lift x) |] , donde lift :: Lift t => t -> Q Exp proviene de la clase

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

Empalmes mecanografiados y citas

  • Los empalmes escritos son similares a los empalmes mencionados anteriormente (sin tipo), y se escriben como $$(..) donde (..) es una expresión.

  • Si e tiene el tipo Q (TExp a) entonces $$e tiene el tipo a .

  • Las citas escritas toman la forma [||..||] donde .. es una expresión de tipo a ; la cita resultante tiene el tipo Q (TExp a) .

  • La expresión escrita se puede convertir a una sin tipo: unType :: TExp a -> Exp .

Cuasi Citas

  • QuasiQuotes generaliza las citas de expresión: anteriormente, el analizador utilizado por la expresión de la cita es uno de un conjunto fijo ( e,p,t,d ), pero QuasiQuotes permite definir un analizador personalizado y utilizarlo para producir código en el momento de la compilación. Las casi comillas pueden aparecer en todos los mismos contextos que las citas regulares.

  • Una casi cita se escribe como [iden|...|] , donde iden es un identificador de tipo Language.Haskell.TH.Quote.QuasiQuoter .

  • Un QuasiQuoter está compuesto simplemente por cuatro analizadores, uno para cada uno de los diferentes contextos en los que pueden aparecer las citas:

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

Los nombres

  • Los identificadores de Haskell están representados por el tipo Language.Haskell.TH.Syntax.Name . Los nombres forman las hojas de sintaxis abstracta que representan los programas de Haskell en la Plantilla Haskell.

  • Un identificador que se encuentra actualmente dentro del alcance puede convertirse en un nombre con: 'e o 'T En el primer caso, e se interpreta en el ámbito de expresión, mientras que en el segundo caso, T está en el ámbito de tipo (recordando que los constructores de tipos y valores pueden compartir el nombre sin ambigüedad en Haskell).



Modified text is an extract of the original Stack Overflow Documentation
Licenciado bajo CC BY-SA 3.0
No afiliado a Stack Overflow