次の方法で共有


コード引用符

この記事では、F# コード式をプログラムで生成して操作できる言語機能であるコード クォートについて説明します。 この機能を使用すると、F# コードを表す抽象構文ツリーを生成できます。 その後、抽象構文ツリーは、アプリケーションのニーズに応じて走査および処理できます。 たとえば、ツリーを使用して F# コードを生成したり、他の言語でコードを生成したりできます。

引用符で囲まれた式

引用符で囲まれた式は、コード内の F# 式であり、プログラムの一部としてコンパイルされないように区切られますが、代わりに F# 式を表すオブジェクトにコンパイルされます。 引用符で囲まれた式は、型情報を使用するか、型情報なしでマークするかの 2 つの方法のいずれかでマークできます。 型情報を含める場合は、 <@ 記号と @> を使用して、引用符で囲まれた式を区切ります。 型情報が不要な場合は、シンボル <@@@@>を使用します。 次のコードは、型指定された引用符と型指定されていない引用符を示しています。

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

型情報を含めなければ、大きな式ツリーの走査が高速になります。 型指定されたシンボルで引用符で囲まれた式の結果の型が Expr<'T>されます。ここで、型パラメーターには、F# コンパイラの型推論アルゴリズムによって決定される式の型があります。 型情報なしでコード引用符を使用する場合、引用符で囲まれた式の型は非ジェネリック型 Expr です。 型指定された Expr クラスの Raw プロパティを呼び出して、型指定されていないExpr オブジェクトを取得できます。

引用符で囲まれた式を使用せずに、 Expr クラスで F# 式オブジェクトをプログラムで生成できるさまざまな静的メソッドがあります。

コード引用符には、完全な式を含める必要があります。 たとえば、 let バインドの場合は、バインド名の定義と、バインドを使用する別の式の両方が必要です。 詳細構文では、これは in キーワードに続く式です。 モジュールの最上位レベルでは、これはモジュールの次の式にすぎませんが、引用符では明示的に必要です。

したがって、次の式は無効です。

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

ただし、次の式は有効です。

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

F# 見積もりを評価するには、 F# 見積エバリュエーターを使用する必要があります。 F# 式オブジェクトの評価と実行をサポートします。

F# の引用符では、型制約情報も保持されます。 次の例を確認してください。

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

inline関数によって生成された制約は、コード引用に保持されます。 negate関数の引用符で囲まれたフォームを評価できるようになりました。

Expr 型

Expr型のインスタンスは、F# 式を表します。 ジェネリック型と非ジェネリック Expr 型の両方が、F# ライブラリのドキュメントに記載されています。 詳細については、「 FSharp.Quotations 名前空間Quotations.Expr クラス」を参照してください。

スプライシング演算子

スプライシングを使用すると、リテラル コード引用符と、プログラムで作成した式、または別のコード引用符から作成した式を組み合わせることができます。 %演算子と%%演算子を使用すると、F# 式オブジェクトをコード引用符に追加できます。 %演算子を使用して、型指定された式オブジェクトを型指定された引用符に挿入します。型指定されていない式オブジェクトを型指定されていない引用符に挿入するには、%%演算子を使用します。 どちらの演算子も単項プレフィックス演算子です。 したがって、 expr が型 Exprの型指定されていない式である場合、次のコードが有効です。

<@@ 1 + %%expr @@>

exprExpr<int>型の型指定された引用符である場合、次のコードが有効です。

<@ 1 + %expr @>

例 1

説明

次の例は、コード引用符を使用して F# コードを式オブジェクトに配置し、式を表す F# コードを出力する方法を示しています。 F# 式オブジェクト (Expr 型) をフレンドリ形式で表示する再帰関数printを含む関数printlnが定義されています。 FSharp.Quotations.Patterns モジュールと FSharp.Quotations.DerivedPatterns モジュールには、式オブジェクトの分析に使用できるアクティブなパターンがいくつかあります。 この例には、F# 式に表示される可能性のあるすべてのパターンが含まれているわけではありません。 認識されないパターンでは、ワイルドカード パターン (_) との一致がトリガーされ、 ToString メソッドを使用してレンダリングされます。 Expr 型では、一致式に追加するアクティブなパターンを知らせます。

Code

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

アウトプット

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

例 2

説明

ExprShape モジュールの 3 つのアクティブなパターンを使用して、アクティブなパターンが少ない式ツリーを走査することもできます。 これらのアクティブ パターンは、ツリーを走査するが、ほとんどのノードのすべての情報が必要ない場合に便利です。 これらのパターンを使用する場合、F# 式は次の 3 つのパターンのいずれかに一致します。式が変数の場合は ShapeVar 、式がラムダ式の場合は ShapeLambda 、式がそれ以外の場合は ShapeCombination 。 前のコード例のようにアクティブなパターンを使用して式ツリーを走査する場合は、可能なすべての F# 式型を処理するためにさらに多くのパターンを使用する必要があり、コードはより複雑になります。 詳細については、「 ExprShape.ShapeVar|ShapeLambda|ShapeCombination アクティブ パターン

次のコード例は、より複雑なトラバーサルの基礎として使用できます。 このコードでは、 add関数呼び出しを含む式の式ツリーが作成されます。 SpecificCall アクティブ パターンは、式ツリー内のaddの呼び出しを検出するために使用されます。 このアクティブパターンは、呼び出しの引数を exprList 値に割り当てます。 この場合、2 つしかないため、これらはプルアウトされ、関数は引数に対して再帰的に呼び出されます。 結果は、スプライス演算子 (%%) を使用してmulの呼び出しを表すコード引用符に挿入されます。 前の例の println 関数を使用して、結果を表示します。

他のアクティブパターン分岐のコードは同じ式ツリーを再生成するだけです。そのため、結果の式の唯一の変更は、 add から mulへの変更です。

Code

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

アウトプット

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

こちらも参照ください