Application
F# quotation expression으로 무엇을 할 수 있을까? 예를 들면, F# Code를 특정 타입으로 바꾸어 보자.
우선 다음과 같은 DSL이 있다고 하자.
| // e := n | e + e | e
* e type exp = Num of int | Add of exp * exp | Mul of exp * exp |
이 DSL의 실행기는 다음과 같다.
let rec
eval exp = |
예를 들면 다음과 같다:
| > eval (Add (Mul (Num 5, Add (Num 4,
Num 2)), Num(2))) val it : int = 32 |
이제 F# code를 DSL로 변환하는 것을 생각해 보자. 예상대로 Quotation을 이용한다:
> let rec
trans code = |
사용예를 보자:
> trans <@ 1 + 2 @> > trans <@ 2 * 3 + 4 @> val it : exp = Mul (Add (Num 2,Num 10),Mul (Num 3,Add (Add (Num 2,Mul (Num 6,Num 7)),Num 2))) |
trans 함수는 F# code를 받아서 이를 DSL로 변경시켜준다. 따라서, 실행기에 넘길 수 있다:
> eval <| trans <@ 1 + 2
@> > eval <| trans <@ 2 * 3
+ 4 @> > eval |< trans <@ (2 +
10) * ( 3 * ( 2 + 6 * 7 + 2 )) @> |
이제 다시 큰그림을 그려 보자. 우리에게 특정 언어와 그 언어 실행기가 있다면, quotation은 F# code를 그 실행기에서 실행할 수 있는 방법을 제공해 주고 있는 것이다. 즉, .NET 이외의 다른 플랫폼에서 실행할 수 있는 방법을 제공해 준다는 것인데, F# code로 작성된 프로그램을 GPU에서 돌리거나, 자동으로 SQL을 생성한다는지, JavaScript으로 변환하는 프로젝트들이 이런 방법을 쓴 예가 되겠다.
Generating Quotation Expressions
이번에는 quotation expression을 생성하는 것에 대해서 살펴 보자. 기본적으로 active pattern을 이용해서 quotation을 분해할 수 있었는데, 그 반대로 quotation을 생성하는 static method들이 있다. 예를 들어서, 다음을 보자:
| > let a = <@ let x = (1, 2, 3) in (x, x) @> val a : Expr<(int * int * int) * (int * int * int)> = Let (x, NewTuple (Value (1), Value (2), Value (3)), NewTuple (x, x)) |
동일한 Expr<_> 값을 다음처럼 생성할 수 있다:
> let b = |
Expression Hole
Quotation은 expression hole이라고 불리는 것을 포함할 수 있는데, 이는 Expr<_> 값이 들어갈 placeholder같은 개념이다. 간단한 예를 보자.
| > let addTwoQuotations x y = <@
%x + %y @> val addTwoQuotations : Quotations.Expr<int> -> Quotations.Expr<int> -> Quotations.Expr<int> > addTwoQuotations <@ 1 @> <@ 2 @> val it : Quotations.Expr<int> = Call (None, op_Addition, [Value (1), Value (2)]) {CustomAttributes = [NewTuple (Value ("DebugRange"), NewTuple (..))]; Raw = ...; Type = System.Int32;} |
위에서 x, y 변수는 Expr<int> 타입을 갖는다. 좀더 복잡한 값을 넘길 수도 있다:
| > addTwoQuotations
<@ [1; 2; 3] |> List.fold (+) 0 @> <@ "hello".Length @> |
즉, 타입이 int를 갖는 F# code block을 인자로 받아서 미리 준비된 quotation expression과 합성을 할 때 쓰인다.
Evaluation Quotation
그럼, 이렇게 작성된 quotation expression을 실행하거나 값을 구하는 방법은 없을까? F# PowerPack을 사용하면 F# quotation expression의 값을 구하거나 실행할 수 있다. 위의 예제를 값을 구해보자.
(* in fsi, open Microsoft.FSharp.Linq.QuotationEvaluation let addTwoQuotations x y = <@ %x +
%y @> > x.Eval() > y.Eval() |
이때, Eval 함수는 unit -> int의 타입을 갖는다. Eval 이외에도 Compile 함수가 제공된다:
| > let
z = x.Compile() val z : (unit -> int) > z () |
Compile 함수는 unit -> unit -> int의 타입을 갖는다.
마치며
Quotation은 컴파일러에게 "이 부분은 코드를 생성하지 말고, 대신에 expression tree를 만들라"는 것인 셈이다. 구해진 expression tree로 앞에서 살펴본대로 다양하게 목적에 맞게 사용할 수 있다. 한가지 문제는 Quotation expression은 F# 컴파일러가 알아 볼 수 있는 형태, 즉, F# code여야 한다는 점이다. 하나의 소스(F# code)로 다양한 플랫폼에서 실행가능하게 하고 싶은 경우에 적합하지만, 전혀 다른 문법의 언어를 사용하고 싶다면 lexer나 parser가 필요하겠다.


