この記事では、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 @@>
expr
がExpr<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))
こちらも参照ください
.NET