Comillas de código
En este artículo se describen las comillas de código, una característica del 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 abstracta que representa el código de F#. A continuación, el árbol de sintaxis abstracto se puede recorrer y procesar según las necesidades de la aplicación. Por ejemplo, puede usar el árbol para generar código de F# o generar código en otro lenguaje.
Expresiones entre comillas
Una expresión entre comillas es una expresión de F# en el código que está delimitada de forma 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 se incluye información de tipo. El tipo resultante de una expresión entrecomillada con los símbolos con tipo es , donde el parámetro de tipo tiene el tipo de la expresión determinado por el algoritmo de inferencia de tipos del compilador de Expr<'T> 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 tipo para obtener el objeto Expr 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 enlace, por ejemplo, necesita la definición del nombre enlazado y let otra expresión que use el enlace. En la sintaxis detallada, se trata de una expresión que sigue a la palabra in clave . En el nivel superior de un módulo, esta es solo la siguiente expresión del módulo, pero, entre comillas, se requiere explícitamente.
Por lo tanto, la siguiente expresión 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 comillas 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 tipos. 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 negate se puede evaluar el formulario entre comillas de la función.
Tipo expr
Una instancia del tipo Expr 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 y Quotations.Expr Class.
Operadores de plicación
La combinación permite combinar comillas de código literales con expresiones creadas mediante programación o a partir de otras comillas de código. Los % operadores y permiten agregar un objeto de expresión de %% 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 es una expresión sin tipo expr de tipo , el código siguiente es Expr válido.
<@@ 1 + %%expr @@>
Y si expr es una comilla con tipo de tipo , el código siguiente es Expr<int> 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 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 que contiene una función recursiva que muestra un objeto de expresión de println print F# (de tipo ) en un formato Expr 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. Este ejemplo no incluye todos los patrones posibles que podrían 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 método , que, en el tipo , le permite conocer el patrón activo que se va a agregar a la expresión _ ToString de Expr 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 @@>
Resultados
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 del módulo ExprShape para recorrer árboles de expresión con menos patrones activos. Estos patrones activos pueden ser útiles cuando se desea recorrer 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: si la expresión es una variable, si la expresión es una expresión lambda o si la expresión es cualquier ShapeVar ShapeLambda otra ShapeCombination cosa. Si recorre un árbol de expresión mediante los patrones activos como en el ejemplo de código anterior, tendrá que usar muchos más patrones para controlar 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 Active Pattern.
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 expresión. Este patrón activo asigna los argumentos de la llamada al exprList valor . En este caso, solo hay dos, por lo que se extrajen y se llama a la función de forma recursiva en los argumentos. Los resultados se insertan en una comilla de código que representa una llamada a mediante mul el operador splice ( %% ). La println función del ejemplo anterior se usa para mostrar los resultados.
El código de las otras ramas de patrón activas 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
Salida
1 + Module1.add(2,Module1.add(3,4))
1 + Module1.mul(2,Module1.mul(3,4))