コード クォート (F#)
このトピックでは、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 @@>
型情報を含めない場合は、大きい式ツリーの走査が速くなります。型指定されたシンボルで囲まれた式の型は、型パラメーターが F# コンパイラの型推論アルゴリズムによって決定される式の型である Expr<'T> になります。型情報を指定せずにコード クォートを使用する場合、クォートで囲まれた式の型は非ジェネリック型の 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
@>
コード クォートを使用するには、Microsoft.FSharp.Quotations 名前空間を開く (open キーワードを使用した) インポート宣言を追加する必要があります。
F# PowerPack では、F# 式オブジェクトの評価と実行がサポートされています。
Expr 型
Expr 型のインスタンスは、F# の式を表します。ジェネリックと非ジェネリックの Expr 型については、F# ライブラリのドキュメントを参照してください。詳細については、「Microsoft.FSharp.Quotations 名前空間 (F#)」および「Quotations.Expr クラス (F#)」を参照してください。
スプライス演算子
スプライスを使用すると、リテラル コード クォートを、プログラムで作成した式または別のコード クォートから作成した式と結合できます。% および %% 演算子を使用すると、F# 式オブジェクトをコード クォートに追加できます。型指定された式オブジェクトを、型指定された引用符に挿入するには、% 演算子を使用します。型指定されていない式オブジェクトを、型指定されていない引用符に挿入するには、%% 演算子を使用します。どちらの演算子も単項前置演算子です。したがって、expr が Expr 型の型指定されていない式の場合は、次のコードが有効です。
<@@ 1 + %%expr @@>
また、expr が Expr<int> 型の型指定された式の場合は、次のコードが有効です。
<@ 1 + %expr @>
例
Description
次の例では、コード クォートを使用して F# コードを式オブジェクトに挿入し、式を表す F# コードを出力します。関数 println の定義には、(Expr 型の) F# 式オブジェクトをわかりやすい形式で表示する再帰関数 print が含まれます。Microsoft.FSharp.Quotations.Patterns モジュールおよび Microsoft.FSharp.Quotations.DerivedPatterns モジュールには、式オブジェクトの分析に使用できる複数のアクティブ パターンがあります。この例には、F# 式に出現する可能性のあるすべてのパターンは含まれていません。認識されないパターンがあると、ワイルドカード パターン (_) に対する一致がトリガーされ、このようなパターンは、ToString メソッドを使用して表示されます。これにより、Expr 型について、match 式に追加する必要のあるアクティブ パターンがわかります。
コード
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
例
Description
また、ExprShape モジュールの 3 つのアクティブ パターンを使用すると、より少ないアクティブ パターンで式ツリーを走査できます。これらのアクティブ パターンは、ツリーを走査する必要はあるものの、ほとんどのノードではすべての情報が必要なわけではない場合に便利です。これらのパターンを使用すると、すべての F# 式は、次の 3 つのパターンのいずれかと一致します。式が変数の場合は ShapeVar と一致し、式がラムダ式の場合は ShapeLambda と一致し、これら以外のすべての式は ShapeCombination と一致します。前に示したコード例のようにアクティブ パターンを使用して式ツリーを走査する場合は、さらに多くのパターンを使用して、可能性のあるすべての F# 式の型を処理する必要があり、コードはさらに複雑になります。詳細については、「ExprShape.ShapeVar|ShapeLambda|ShapeCombination アクティブ パターン (F#)」を参照してください。
次に示すコード例を基にして、さらに複雑な走査を作成できます。このコードでは、関数呼び出し add を含む式の式ツリーを作成しています。SpecificCall アクティブ パターンを使用して、式ツリー内にある add のすべての呼び出しを検出します。このアクティブ パターンは、呼び出しの引数を exprList 値に割り当てます。この例では 2 つのみであり、それらが取り出されて、関数が引数に対して再帰的に呼び出されます。結果は、スプライス演算子 (%%) を使用して、mul の呼び出しを表すコード クォートに挿入されます。前の例の println 関数を使用して、結果を表示します。
他のアクティブ パターン分岐のコードは同じ式ツリーを再生成するだけなので、結果の式での変更は、add から mul への変更だけです。
コード
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))