코드 인용(F#)

업데이트: 2010년 5월

이 항목에서는 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 개체를 얻을 수 있습니다.

인용된 식을 사용하지 않고 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
@>

코드 인용을 사용하려면 open 키워드를 사용하여 Microsoft.FSharp.Quotations 네임스페이스를 여는 가져오기 선언을 추가해야 합니다.

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

예제

설명

다음 예제에서는 코드 인용을 사용하여 F# 코드를 식 개체에 추가한 다음 식을 나타내는 F# 코드를 출력하는 방법을 보여 줍니다. Expr 형식의 F# 식 개체를 사용자가 쉽게 알 수 있는 형태로 표시하는 재귀 함수 print를 포함하는 println 함수가 정의됩니다. Microsoft.FSharp.Quotations.PatternsMicrosoft.FSharp.Quotations.DerivedPatterns 모듈에는 식 개체를 분석하는 데 사용할 수 있는 여러 가지 활성 패턴이 있습니다. 이 예제에는 F# 식에 나올 수 있는 패턴 중 일부만 포함되어 있습니다. 인식되지 않은 패턴이 있으면 와일드카드 패턴(_)에 대한 일치가 트리거되고 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 @@>

Output

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

예제

설명

ExprShape 모듈의 세 가지 활성 패턴을 사용하여 더 적은 수의 활성 패턴으로 식 트리를 따라 이동할 수도 있습니다. 이러한 활성 패턴은 트리를 따라 이동해야 하지만 대부분의 노드에서 일부 정보만 필요한 경우에 유용하게 사용할 수 있습니다. 이러한 패턴을 사용하면 F# 식이 세 가지 패턴 중 하나에 일치하게 됩니다. 이때 식이 변수이면 ShapeVar에, 식이 람다 식이면 ShapeLambda에, 식이 그 밖의 다른 항목이면 ShapeCombination에 일치하게 됩니다. 위 코드 예제에서와 같은 활성 패턴을 사용하여 식 트리를 따라 이동하면 모든 가능한 F# 식 형식을 처리하기 위해 훨씬 더 많은 패턴을 사용해야 하므로 코드가 더 복잡해질 수 있습니다. 자세한 내용은 ExprShape.ShapeVar|ShapeLambda|ShapeCombination 활성 패턴(F#)을 참조하십시오.

더 복잡한 이동을 위한 기본 출발점으로 다음 코드 예제를 사용할 수 있습니다. 이 코드에서는 add 함수 호출이 관련된 식에 대한 식 트리가 만들어집니다. 식 트리에서 add 에 대한 호출을 감지하는 데는 SpecificCall 활성 패턴이 사용됩니다. 이 활성 패턴은 호출의 인수를 exprList 값에 할당합니다. 이 경우에는 인수가 두 개뿐이므로 이들 인수를 가져오고 각 인수에 대해 함수가 재귀적으로 호출됩니다. 결합 연산자(%%)를 사용하여 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

Output

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

참고 항목

기타 리소스

F# 언어 참조

변경 기록

날짜

변경 내용

이유

2010년 5월

마지막 코드 예제가 수정되었습니다.

콘텐츠 버그 수정