Kutipan kode

Artikel ini menjelaskan kutipan kode, fitur bahasa pemrogram yang memungkinkan Anda membuat dan menggunakan ekspresi kode F# secara terprogram. Fitur ini memungkinkan Anda menghasilkan pohon sintaks abstrak yang mewakili kode F#. Pohon sintaks abstrak kemudian dapat dilintasi dan diproses sesuai dengan kebutuhan aplikasi Anda. Misalnya, Anda dapat menggunakan pohon untuk menghasilkan kode F# atau menghasilkan kode dalam beberapa bahasa pemrogram lain.

Ekspresi yang dikutip

Ekspresi yang dikutip adalah ekspresi F# dalam kode Anda yang dibatasi agar tidak dikompilasi sebagai bagian dari program Anda, tetapi dikompilasi ke dalam objek yang mewakili ekspresi F#. Anda dapat menandai ekspresi yang dikutip dengan salah satu dari dua cara: dengan informasi jenis atau tanpa informasi jenis. Jika Anda ingin menyertakan informasi jenis, gunakan simbol <@ dan @> untuk memisahkan ekspresi yang dikutip. Jika Anda tidak memerlukan informasi jenis, gunakan simbol <@@ dan @@>. Kode berikut menunjukkan kutipan berjenis dan tidak berjenis.

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

Melintasi pohon ekspresi besar akan lebih cepat jika Anda tidak menyertakan informasi jenis. Jenis ekspresi yang dihasilkan yang dikutip dengan simbol berjenis adalah Expr<'T>, di mana parameter jenis memiliki jenis ekspresi seperti yang ditentukan oleh algoritma inferensi jenis pengompilasi F#. Saat Anda menggunakan kutipan kode tanpa informasi jenis, jenis ekspresi yang dikutip adalah Expr jenis non-generik. Anda dapat memanggil properti Raw (Mentah) pada kelas Expr yang berjenis untuk mendapatkan objek Expr yang tidak berjenis.

Ada berbagai metode statik yang memungkinkan Anda menghasilkan objek ekspresi F# secara terprogram di kelas Expr tanpa menggunakan ekspresi yang dikutip.

Kutipan kode harus menyertakan ekspresi lengkap. Untuk pengikatan let, misalnya, Anda memerlukan definisi nama terikat dan ekspresi lain yang menggunakan pengikatan. Dalam sintaks verbose, ini adalah ekspresi yang mengikuti kata kunci in. Di tingkat atas dalam modul, ini hanyalah ekspresi berikutnya dalam modul, tetapi dalam kutipan, secara eksplisit diperlukan.

Oleh karena itu, ekspresi berikut tidak valid.

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

Namun, ekspresi berikut valid.

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

Untuk mengevaluasi kutipan F#, Anda harus menggunakan F# Quotation Evaluator (Evaluator Kutipan F#). Ini memberikan dukungan untuk mengevaluasi dan mengeksekusi objek ekspresi F#.

Kutipan F# juga menyimpan informasi batasan jenis. Pertimbangkan contoh berikut:

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

Batasan yang dibuat oleh fungsi inline dipertahankan dalam kutipan kode. Formulir yang dikutip fungsi negate sekarang dapat dievaluasi.

Jenis expr

Instans jenis Expr mewakili ekspresi F#. Jenis Expr generik dan non-generik didokumenkan dalam dokumentasi pustaka F#. Untuk informasi selengkapnya, lihat FSharp.Quotations Namespace dan Quotations.Expr Class.

Operator splicing

Splicing memungkinkan Anda menggabungkan kutipan kode harfiah dengan ekspresi yang telah dibuat secara terprogram atau dari kutipan kode lain. Operator % dan %% memungkinkan Anda menambahkan objek ekspresi F# ke dalam kutipan kode. Gunakan operator % untuk menyisipkan objek ekspresi berjenis ke dalam kutipan berjenis; gunakan operator %% untuk menyisipkan objek ekspresi tidak berjenis ke dalam kutipan tidak berjenis. Kedua operator adalah operator prefiks tunggal. Jadi, jika expr adalah ekspresi tidak berjenis dari jenis Expr, kode berikut valid.

<@@ 1 + %%expr @@>

Dan jika expr adalah kutipan berjenis dari jenis Expr<int>, kode berikut valid.

<@ 1 + %expr @>

Contoh 1

Deskripsi

Contoh berikut mengilustrasikan penggunaan kutipan kode untuk menempatkan kode F# ke dalam objek ekspresi lalu mencetak kode F# yang mewakili ekspresi. Fungsi println yang ditentukan yang berisi fungsi print rekursif yang menampilkan objek ekspresi F# (dari jenis Expr) dalam format yang familier. Ada beberapa pola aktif dalam modul FSharp.Quotations.Patterns dan FSharp.Quotations.DerivedPatterns yang dapat digunakan untuk menganalisis objek ekspresi. Contoh ini tidak menyertakan semua pola yang mungkin muncul dalam ekspresi F#. Setiap pola yang tidak dikenali memicu kecocokan dengan pola wildcard (_) dan di-render menggunakan metode ToString, yang pada jenis Expr, memungkinkan Anda mengetahui pola aktif untuk ditambahkan ke ekspresi pencocokan.

Kode

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 @@>

Hasil

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

Contoh 2

Deskripsi

Anda juga dapat menggunakan tiga pola aktif dalam modul ExprShape untuk melintasi pohon ekspresi dengan lebih sedikit pola aktif. Pola aktif ini dapat berguna ketika Anda ingin melintasi pohon tetapi tidak memerlukan semua informasi di sebagian besar simpul. Saat Anda menggunakan pola ini, semua ekspresi F# cocok dengan salah satu dari tiga pola berikut: ShapeVar jika ekspresi adalah variabel, ShapeLambda jika ekspresi adalah ekspresi lambda, atau ShapeCombination jika ekspresinya adalah ekspresi lain. Jika melintasi pohon ekspresi menggunakan pola aktif seperti dalam contoh kode sebelumnya, Anda harus menggunakan lebih banyak pola untuk menangani semua jenis ekspresi F#, dan kode Anda akan lebih kompleks. Untuk informasi selengkapnya, lihat Pola Aktif ExprShape.ShapeVar| ShapeLambda| ShapeCombination.

Contoh kode berikut dapat digunakan sebagai dasar untuk pelintasan yang lebih kompleks. Dalam kode ini, pohon ekspresi dibuat untuk ekspresi yang melibatkan panggilan fungsi, add. Pola aktif SpecificCall digunakan untuk mendeteksi semua panggilan ke add di pohon ekspresi. Pola aktif ini menetapkan argumen panggilan ke nilai exprList. Dalam hal ini, hanya ada dua, sehingga ditarik keluar dan fungsi dipanggil secara rekursif pada argumen. Hasilnya disisipkan ke dalam kutipan kode yang mewakili panggilan ke mul menggunakan operator splice (%%). Fungsi println dari contoh sebelumnya digunakan untuk menampilkan hasilnya.

Kode di cabang pola aktif lainnya hanya meregenerasi pohon ekspresi yang sama, sehingga satu-satunya perubahan dalam ekspresi yang dihasilkan adalah perubahan dari add ke mul.

Kode

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

Hasil

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

Lihat juga