다음을 통해 공유


코드 인용

이 문서에서는 프로그래밍 방식으로 F# 코드 식을 생성하고 사용할 수 있는 언어 기능인 코드 따옴표에 대해 설명합니다. 이 기능을 사용하면 F# 코드를 나타내는 추상 구문 트리를 생성할 수 있습니다. 그런 다음 추상 구문 트리를 탐색하고 애플리케이션의 요구에 따라 처리할 수 있습니다. 예를 들어 트리를 사용하여 F# 코드를 생성하거나 다른 언어로 코드를 생성할 수 있습니다.

따옴표 붙은 식

따옴표 붙은 식은 프로그램의 일부로 컴파일되지 않고 대신 F# 식을 나타내는 개체로 컴파일되는 방식으로 구분되는 코드의 F# 식입니다. 따옴표 붙은 식을 형식 정보 또는 형식 정보 없이 두 가지 방법 중 하나로 표시할 수 있습니다. 형식 정보를 포함하려면 기호를 <@@> 사용하고 따옴표 붙은 식을 구분합니다. 형식 정보가 필요하지 않은 경우 기호와 <@@@@>. 다음 코드는 형식화된 따옴표와 형식화되지 않은 따옴표를 보여 줍니다.

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 화되지 않은 개체를 가져올 수 있습니다.

따옴표 붙은 식을 사용하지 않고 클래스에서 프로그래밍 방식으로 F# 식 개체를 Expr 생성할 수 있는 다양한 정적 메서드가 있습니다.

코드 따옴표에는 완전한 식이 포함되어야 합니다. 예를 들어 바인딩의 let 경우 바인딩 이름의 정의와 바인딩을 사용하는 다른 식이 모두 필요합니다. 자세한 구문에서 이 식은 키워드(keyword) 따르는 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<int>이 지정된 따옴표인 경우 expr 다음 코드가 유효합니다.

<@ 1 + %expr @>

예 1

설명

다음 예제에서는 코드 따옴표를 사용하여 F# 코드를 식 개체에 넣은 다음 식을 나타내는 F# 코드를 인쇄하는 방법을 보여 줍니다. F# 식 개체(형식Expr)를 친숙한 형식으로 표시하는 재귀 함수를 포함하는 함수 printlnprint 가 정의됩니다. FSharp.Quotations.Patterns 및 FSharp.Quotations.DerivedPatterns 모듈에는 식 개체를 분석하는 데 사용할 수 있는 몇 가지 활성 패턴이 있습니다. 이 예제에서는 F# 식에 나타날 수 있는 모든 가능한 패턴을 포함하지 않습니다. 인식할 수 없는 패턴은 wild카드 패턴(_)과 일치를 트리거하고 메서드를 사용하여 렌더링됩니다. 이 메서드를 사용하면 ToString 형식에서 일치 식에 Expr 추가할 활성 패턴을 알 수 있습니다.

코드

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 모듈세 가지 활성 패턴을 사용하여 더 적은 활성 패턴으로 식 트리를 트래버스할 수도 있습니다. 이러한 활성 패턴은 트리를 트래버스하려는 경우 유용할 수 있지만 대부분의 노드에서 모든 정보가 필요하지는 않습니다. 이러한 패턴을 사용하는 경우 F# 식은 다음 세 가지 패턴 중 하나와 일치합니다 ShapeVar . 식이 변수인 경우, ShapeLambda 식이 람다 식인 경우 또는 ShapeCombination 식이 다른 경우입니다. 이전 코드 예제와 같이 활성 패턴을 사용하여 식 트리를 트래버스하는 경우 가능한 모든 F# 식 형식을 처리하기 위해 더 많은 패턴을 사용해야 하며 코드가 더 복잡해집니다. 자세한 내용은 ExprShape.ShapeVar|ShapeLambda|ShapeCombination 활성 패턴입니다.

다음 코드 예제는 더 복잡한 순회의 기초로 사용할 수 있습니다. 이 코드에서는 함수 호출 add을 포함하는 식에 대한 식 트리를 만듭니다. SpecificCall 활성 패턴은 식 트리에서 호출을 검색하는 add 데 사용됩니다. 이 활성 패턴은 값에 대한 호출의 인수를 할당합니다 exprList . 이 경우 두 개만 있으므로 인수를 끌어오고 인수에서 함수를 재귀적으로 호출합니다. 결과는 splice 연산자()를 사용하여 호출을 mul 나타내는 코드 따옴표에%% 삽입됩니다. println 이전 예제의 함수는 결과를 표시하는 데 사용됩니다.

다른 활성 패턴 분기의 코드는 동일한 식 트리만 다시 생성하므로 결과 식의 유일한 변경 내용은 변경 내용mul입니다add.

코드

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

참고 항목