基于引用的元编程(Metaprogramming with Quotations)

728 篇文章 1 订阅
86 篇文章 0 订阅

基于引用的元编程(Metaprogramming with Quotations)

 

在第六章,我们使用过引用(quotations),它们是被引用的 F# 代码段,引用运算符指示编译器生成表示代码的数据结构,而不是表示代码的中间语言(IL)。即,生成了表示代码的数据结构,而不是可执行代码,这样,就可以很方便地用它做你想做的事情。可以解释,执行一些动作,也可以编译成另一种语言,或者,干脆忽略它。例如,可以引用一段代码,把它编译成另一种运行时,如Java 虚拟机(JVM),或者,如第九章中的LINQ 示例一样,把它转换成 SQL,在数据库中运行。

在下面的例子中,我们将用 F# 写一个基于整数算术表达式的解析器,这对于学习如何基于堆栈进行计算是有用的。在这里,语言已经设计好了,它就是 F # 中可用的语法,所要做的就是这个算术表达式 <@ (2 * (2 - 1)) / 2 @>。就是说,无论什么时候,遇到不是整数,或不是运算符的语法时,就报错。引用是基于差别联合类型的,当使用引用时,必须通过 F# 的模式匹配和活动模式,查询接收到的表达式。例如,这里查询表达式,是通过活动模式和一个 when 子句来看它是否是整数,如果是,就压栈:

 

|Value (x,ty)when ty = typeof<int>->

                                        let i = x :?>int

                                        printfn"Push %i" i

                                       operandsStack.Push(x :?> int)

 

如果不是整数,则继续检查看它是否是其他类型。也有几个参数化的活动模式会会很有用,例如,SpecificCall 接收引用作为参数,它是一个函数表达式,它可以用来查询这个引用是否匹配对这个函数的调用。可以用它来检查对一个去处符的调用是否完成,例如,这个示例检查对加法的调用是否完成:

 

|SpecificCall <@ (+) @> (_,_, [l;r]) ->interpretInner l

                                        interpretInner r

                                        preformOp (+) "Add"

 

 

 

清单12-5 是完整的代码。

 

清单12-5 基于栈的 F# 引用算术表达式的评估

 

open System.Collections.Generic

open Microsoft.FSharp.Quotations

open Microsoft.FSharp.Quotations.Patterns

open Microsoft.FSharp.Quotations.DerivedPatterns

 

let interpret exp =

  letoperandsStack = new Stack<int>()

  letpreformOp f name =

    let x,y = operandsStack.Pop(), operandsStack.Pop()

   printfn "%s %i, %i" name x y

    letresult = f x y

    operandsStack.Push(result)

  let recinterpretInner exp =

    matchexp with

    | SpecificCall <@(*) @> (_,_, [l;r]) ->interpretInner l

                                            interpretInner r

                                            preformOp (*) "Mult"

    | SpecificCall <@(+) @> (_,_, [l;r]) ->interpretInner l

                                            interpretInner r

                                            preformOp (+) "Add"

    | SpecificCall <@(-) @> (_,_, [l;r]) ->interpretInner r

                                             interpretInner l

                                            preformOp (-) "Sub"

    | SpecificCall <@(/) @> (_,_, [l;r]) ->interpretInner r

                                             interpretInner l

                                             preformOp(/) "Div"

    | Value (x,ty) when ty =typeof<int>->

                                            let i = x :?> int

                                            printfn "Push: %i" i

                                            operandsStack.Push(x :?> int)

    | _ ->failwith "not a valid op"

 interpretInner exp

  printfn "Result: %i" (operandsStack.Pop())

 

interpret <@(2 * (2 - 1)) / 2 @>                                        

 

运行结果如下:

 

Push: 2

Push: 2

Push: 1

Sub 1, 2

Multi 1, 2

Push: 2

Div 2, 2

Result: 1

 

[

按照原文中的程序,运行结果应为 -1。

栈是先进后出的。

对于除法与减法,顺序是不能颠倒的,即必须是除数(减数)先入栈,也就是 r 称入栈。

当然,实际上,都应该是 r 先入栈。

]

 

在使用引用时,我们总是用 F# 语法,它有好处,也有不足。好处是,可以产生功能强大的库,并且和 F# 代码可以很好地集成,不必要再创建解析器;不足在于,产生适合最终用户使用的工具很困难。然而,使用或转换 F# 引用的库仍然可以用在其他.NET 语言中,因为,F# 库包括了函数和示例,可以在 F# 引用和其他常用的元编程格式之间转换,比如LINQ 引用。引用的更有意义的应用,比如小语言,可参见Microsoft Solver Foundation 上的 F# DSL,http://msdn.microsoft.com/zh-cn/library/hh145003.aspx(由于变动太快,原文地址好像不能用了 http://code.msdn.microsoft.com/solverfoundation),也可以看我博客上的讨论 http://strangelights.com/blog/archive/2008/09/21/1628.aspx

到此,我们就结束了有关 DSL 的讨论,本章的其余部分,将深入探讨有关语言解析器和编译器的实现。

 

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值