Expresiones de código delimitadas (F#)
En este tema, se describen las expresiones de código delimitadas, una característica del lenguaje que permite generar y usar expresiones de código de F# mediante programación.Esta característica permite generar un árbol de sintaxis abstracta que representa el código de F#.Dicho árbol se recorrerá y se procesará según las necesidades de la aplicación.Por ejemplo, se puede utilizar el árbol para generar código de F# o código de algún otro lenguaje.
Expresiones delimitadas
Una expresión delimitada es una expresión de código de F# que se delimita de modo que no se compile como parte del programa sino en un objeto que representa una expresión de F#.Una expresión delimitada se puede marcar de dos maneras: con o sin información de tipo.Para incluir la información de tipo, se utilizan los símbolos <@ y @> para marcar la expresión delimitada.Si no se necesita la información de tipo, se utilizan los símbolos <@@ y @@>.En el siguiente código, se muestran expresiones delimitadas con y sin información de tipo.
open Microsoft.FSharp.Quotations
// A typed code quotation.
let expr : Expr<int> = <@ 1 + 1 @>
// An untyped code quotation.
let expr2 : Expr = <@@ 1 + 1 @@>
Un árbol de expresión grande se recorre más rápidamente si no incluye información de tipo.El tipo resultante de una expresión delimitada con los símbolos de tipo es Expr<'T>, donde el parámetro de tipo tiene el tipo de la expresión determinado por el algoritmo de inferencia de tipos del compilador de F#.Cuando se utilizan expresiones de código delimitadas sin información de tipo, el tipo de dichas expresiones es el tipo no genérico Expr.Se puede invocar la propiedad Raw de la clase Expr con información de tipo para obtener el objeto Expr sin información de tipo.
Hay diversos métodos estáticos que permiten generar objetos de expresión de F# mediante programación en la clase Expr sin utilizar expresiones delimitadas.
Tenga en cuenta que una expresión de código delimitada debe incluir una expresión completa.Por ejemplo, para un enlace let, se necesitan la definición del nombre enlazado y una expresión adicional que utilice el enlace.En la sintaxis detallada, es una expresión que figura después de la palabra clave in.En el nivel superior de un módulo, se trata simplemente de la siguiente expresión del módulo; sin embargo, en una expresión de código delimitada, se requiere explícitamente.
Por consiguiente, la siguiente expresión no es válida.
// Not valid:
// <@ let f x = x + 1 @>
Las siguientes expresiones sí son válidas.
// Valid:
<@ let f x = x + 10 in f 20 @>
// Valid:
<@
let f x = x + 10
f 20
@>
Para poder utilizar expresiones de código delimitadas, es preciso agregar una declaración de importación (mediante la palabra clave open) que abra el espacio de nombres Microsoft.FSharp.Quotations.
F# PowerPack proporciona compatibilidad con la evaluación y la ejecución de los objetos de expresión de F#.
Tipo Expr
Una instancia del tipo Expr representa una expresión de F#.Los tipos Expr genéricos y no genéricos se documentan en la documentación de la biblioteca de F#.Para obtener más información, vea Microsoft.FSharp.Quotations (Espacio de nombres de F#) y Quotations.Expr (Clase de F#).
Operadores de inserción
Estos operadores permiten combinar expresiones de código delimitadas literales con expresiones que se han creado mediante programación o a partir de otra expresión de código delimitada.Los operadores % y %% permiten insertar un objeto de expresión de F# en una expresión de código delimitada.El operador % se utiliza para insertar un objeto de expresión con tipo en una expresión de código delimitada con tipo; el operador %% se utiliza para insertar un objeto de expresión sin tipo en una expresión de código delimitada sin tipo.Ambos operadores son operadores prefijos unarios.Por consiguiente, si expr es una expresión sin tipo del tipo Expr, el siguiente código será válido.
<@@ 1 + %%expr @@>
Y si expr es una expresión de código delimitada con tipo del tipo Expr<int>, el siguiente código será válido.
<@ 1 + %expr @>
Ejemplo
Descripción
En el siguiente ejemplo, se muestra el uso de expresiones delimitadas para insertar código de F# en un objeto de expresión y, a continuación, imprimir el código de F# que representa la expresión.Se define una función println que contiene una función recursiva print que muestra un objeto de expresión de F# (del tipo Expr) con un formato fácil de usar.En los módulos Microsoft.FSharp.Quotations.Patterns y Microsoft.FSharp.Quotations.DerivedPatterns, hay varios modelos activos que se pueden utilizar para analizar los objetos de expresión.En este ejemplo no se incluyen todos los posibles modelos que podrían aparecer en una expresión de F#.Cualquier modelo no reconocido desencadena una coincidencia con el modelo de carácter comodín (_) y se representa mediante el método ToString que, en el tipo Expr, identifica el modelo 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 @@>
Output
fun (x:System.Int32) -> x + 1
a + 1
let f = fun (x:System.Int32) -> x + 10 in f 10
Ejemplo
Descripción
También se pueden utilizar los tres modelos activos del módulo ExprShape para recorrer árboles de expresión con menos modelos activos.Estos modelos activos pueden resultar útiles cuando se desea recorrer un árbol pero no se necesita toda la información de la mayoría de los nodos.Cuando se usan estos modelos, las expresiones de F# coinciden con uno de los siguientes tres modelos: ShapeVar si la expresión es una variable, ShapeLambda si la expresión es una expresión lambda, o ShapeCombination si la expresión no es una variable ni una expresión lambda.Si se recorre un árbol de expresión mediante los modelos activos, tal y como se muestra en el ejemplo de código anterior, se deberán utilizar muchos más modelos para administrar todos los posibles tipos de expresión de F# y el código será más complejo.Para obtener más información, vea ExprShape.ShapeVar|ShapeLambda|ShapeCombination (Modelo activo de F#).
El siguiente ejemplo de código se puede utilizar 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.Se utiliza el modelo activo SpecificCall para detectar cualquier llamada a add en el árbol de expresión.Este modelo activo asigna los argumentos de la llamada al valor exprList.En este caso, solo hay dos, por lo que se extraen estos dos argumentos y se invoca la función de manera recursiva en los argumentos.Los resultados se insertan mediante el operador de inserción (%%) en una expresión de código delimitada que representa una llamada a mul.Se utiliza la función println del ejemplo anterior para mostrar los resultados.
El código en las otras bifurcaciones del modelo activo simplemente regenera 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
Output
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))