Compartir a través de


Comillas de código

En este artículo se describen las comillas de código, una característica de lenguaje que permite generar y trabajar con expresiones de código de F# mediante programación. Esta característica le permite generar un árbol de sintaxis abstracto que representa el código de F#. El árbol de sintaxis abstracta se puede recorrer y procesar según las necesidades de la aplicación. Por ejemplo, puede usar el árbol para generar código F# o generar código en algún otro lenguaje.

Expresiones entrecomilladas

Una expresión entre comillas es una expresión de F# en el código delimitada de tal manera que no se compila como parte del programa, sino que se compila en un objeto que representa una expresión de F#. Puede marcar una expresión entre comillas de una de estas dos maneras: con información de tipo o sin información de tipo. Si desea incluir información de tipo, use los símbolos <@ y @> para delimitar la expresión entre comillas. Si no necesita información de tipo, use los símbolos <@@ y @@>. En el código siguiente se muestran las comillas con tipo y sin tipo.

open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>

Recorrer un árbol de expresión grande es más rápido si no incluye información de tipos. El tipo resultante de una expresión entrecomillada con los símbolos con tipo es Expr<'T>, donde el parámetro type tiene el tipo de la expresión según lo determina el algoritmo de inferencia de tipos del compilador de F#. Cuando se usan comillas de código sin información de tipo, el tipo de la expresión entre comillas es el tipo no genérico Expr. Puede llamar a la propiedad Raw en la clase con Expr tipo para obtener el objeto sin Expr tipo.

Hay varios métodos estáticos que permiten generar objetos de expresión de F# mediante programación en la Expr clase sin usar expresiones entre comillas.

Una comilla de código debe incluir una expresión completa. Para un let enlace, por ejemplo, necesita la definición del nombre enlazado y otra expresión que use el enlace. En la sintaxis detallada, se trata de una expresión que sigue a la in palabra clave . En el nivel superior de un módulo, se trata solo de la siguiente expresión del módulo, pero en una comilla, se requiere explícitamente.

Por lo tanto, la expresión siguiente no es válida.

// Not valid:
// <@ let f x = x + 1 @>

Pero las expresiones siguientes son válidas.

// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
    let f x = x + 10
    f 20
@>

Para evaluar las comillas de F#, debe usar el evaluador de citas de F#. Proporciona compatibilidad para evaluar y ejecutar objetos de expresión de F#.

Las comillas de F# también conservan la información de restricción de tipo. Considere el ejemplo siguiente:

open FSharp.Linq.RuntimeHelpers

let eval q = LeafExpressionConverter.EvaluateQuotation q

let inline negate x = -x
// val inline negate: x: ^a ->  ^a when  ^a : (static member ( ~- ) :  ^a ->  ^a)

<@ negate 1.0 @>  |> eval

La restricción generada por la inline función se conserva en la comilla de código. Ahora se puede evaluar el negate formulario entre comillas de la función.

Tipo expr

Una instancia del Expr tipo representa una expresión de F#. Tanto los tipos genéricos como los no genéricos Expr se documentan en la documentación de la biblioteca de F#. Para obtener más información, vea FSharp.Quotations Namespace and Quotations.Expr (Clase).

Operadores de splicing

Splicing permite combinar comillas de código literales con expresiones que ha creado mediante programación o a partir de otra comilla de código. Los % operadores y %% permiten agregar un objeto de expresión F# a una comilla de código. El operador se usa % para insertar un objeto de expresión con tipo en una comilla con tipo; se usa el %% operador para insertar un objeto de expresión sin tipo en una comilla sin tipo. Ambos operadores son operadores de prefijo unario. Por lo tanto, si expr es una expresión sin tipo de tipo Expr, el código siguiente es válido.

<@@ 1 + %%expr @@>

Y si expr es una comilla con tipo de tipo Expr<int>, el código siguiente es válido.

<@ 1 + %expr @>

Ejemplo 1

Descripción

En el ejemplo siguiente se muestra el uso de comillas de código para colocar código de F# en un objeto de expresión y, a continuación, imprimir el código F# que representa la expresión. Se define una función que contiene una función printlnprint recursiva que muestra un objeto de expresión de F# (de tipo Expr) en un formato descriptivo. Hay varios patrones activos en los módulos FSharp.Quotations.Patterns y FSharp.Quotations.DerivedPatterns que se pueden usar para analizar objetos de expresión. En este ejemplo no se incluyen todos los patrones posibles que pueden aparecer en una expresión de F#. Cualquier patrón no reconocido desencadena una coincidencia con el patrón de caracteres comodín (_) y se representa mediante el ToString método , que, en el Expr tipo, le permite saber el patrón activo que se va a agregar a la expresión de coincidencia.

Código

module Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.Patterns
open Microsoft.FSharp.Quotations.DerivedPatterns

let println expr =
    let rec print expr =
        match expr with
        | Application(expr1, expr2) ->
            // Function application.
            print expr1
            printf " "
            print expr2
        | SpecificCall <@@ (+) @@> (_, _, exprList) ->
            // Matches a call to (+). Must appear before Call pattern.
            print exprList.Head
            printf " + "
            print exprList.Tail.Head
        | Call(exprOpt, methodInfo, exprList) ->
            // Method or module function call.
            match exprOpt with
            | Some expr -> print expr
            | None -> printf "%s" methodInfo.DeclaringType.Name
            printf ".%s(" methodInfo.Name
            if (exprList.IsEmpty) then printf ")" else
            print exprList.Head
            for expr in exprList.Tail do
                printf ","
                print expr
            printf ")"
        | Int32(n) ->
            printf "%d" n
        | Lambda(param, body) ->
            // Lambda expression.
            printf "fun (%s:%s) -> " param.Name (param.Type.ToString())
            print body
        | Let(var, expr1, expr2) ->
            // Let binding.
            if (var.IsMutable) then
                printf "let mutable %s = " var.Name
            else
                printf "let %s = " var.Name
            print expr1
            printf " in "
            print expr2
        | PropertyGet(_, propOrValInfo, _) ->
            printf "%s" propOrValInfo.Name
        | String(str) ->
            printf "%s" str
        | Value(value, typ) ->
            printf "%s" (value.ToString())
        | Var(var) ->
            printf "%s" var.Name
        | _ -> printf "%s" (expr.ToString())
    print expr
    printfn ""


let a = 2

// exprLambda has type "(int -> int)".
let exprLambda = <@ fun x -> x + 1 @>
// exprCall has type unit.
let exprCall = <@ a + 1 @>

println exprLambda
println exprCall
println <@@ let f x = x + 10 in f 10 @@>

Salida

fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10

Ejemplo 2

Descripción

También puede usar los tres patrones activos en el módulo ExprShape para atravesar árboles de expresión con menos patrones activos. Estos patrones activos pueden ser útiles cuando se desea atravesar un árbol, pero no se necesita toda la información en la mayoría de los nodos. Cuando se usan estos patrones, cualquier expresión de F# coincide con uno de los tres patrones siguientes: ShapeVar si la expresión es una variable, ShapeLambda si la expresión es una expresión lambda o ShapeCombination si la expresión es otra cosa. Si recorre un árbol de expresiones mediante los patrones activos como en el ejemplo de código anterior, debe usar muchos más patrones para controlar todos los tipos de expresión de F# posibles y el código será más complejo. Para obtener más información, vea ExprShape.ShapeVar|ShapeLambda|Patrón activo ShapeCombination.

El ejemplo de código siguiente se puede usar como base para recorridos más complejos. En este código, se crea un árbol de expresión para una expresión que implica una llamada de función, add. El patrón activo SpecificCall se usa para detectar cualquier llamada a add en el árbol de expresiones. Este patrón activo asigna los argumentos de la llamada al exprList valor . En este caso, solo hay dos, por lo que se extraen y la función se denomina recursivamente en los argumentos. Los resultados se insertan en una comilla de código que representa una llamada a mul mediante el operador splice (%%). La println función del ejemplo anterior se usa para mostrar los resultados.

El código de las otras ramas de patrones activos simplemente vuelve a generar el mismo árbol de expresión, por lo que el único cambio en la expresión resultante es el cambio de add a mul.

Código

module Module1
open Print
open Microsoft.FSharp.Quotations
open Microsoft.FSharp.Quotations.DerivedPatterns
open Microsoft.FSharp.Quotations.ExprShape

let add x y = x + y
let mul x y = x * y

let rec substituteExpr expression =
    match expression with
    | SpecificCall <@@ add @@> (_, _, exprList) ->
        let lhs = substituteExpr exprList.Head
        let rhs = substituteExpr exprList.Tail.Head
        <@@ mul %%lhs %%rhs @@>
    | ShapeVar var -> Expr.Var var
    | ShapeLambda (var, expr) -> Expr.Lambda (var, substituteExpr expr)
    | ShapeCombination(shapeComboObject, exprList) ->
        RebuildShapeCombination(shapeComboObject, List.map substituteExpr exprList)

let expr1 = <@@ 1 + (add 2 (add 3 4)) @@>
println expr1
let expr2 = substituteExpr expr1
println expr2

Salida

1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))

Consulte también