[翻译]F#概述(四)- 面向语言编程
来源:广州中睿信息技术有限公司
发布时间:2012/10/21 23:25:16 编辑:itlead 阅读 1614

 

  在F#里精确的定义面向语言编程并不容易,所以我想用一些例子演示我是怎么理解它的.面向语言编程的目的是为了开发一种适合于解决某些特定问题的语言.当然开发真正的语言是非常复杂的,所以这里有一些使它简化的方法.一个最基本的例子:程序使用xml(包括schema)作为一种语言来解决一些问题(例如配置程序) .

 

  本文中讨论的自定义语言都是F#的子集,在一些文章里你也许见过领域专用语言(domain specific language),它和我们这里讨论的很类似,它可以是外部的(另外一种语言如xml),也可以说内部的(宿主语言的子集).我们将专注于内部 DSL.

 

  因为这个主题不像之前谈过的那么众所周知,我想介绍下为什么它很重要. 我认为面向语言编程成为吸引人的典范的一个重要的原因是它允许人们顺利合作,有开发语言的人和使用的人. 开发者需要很熟悉技术(F#)和这个语言试图解决的问题(如数学处理),但不需要知道所有问题.另一方面使用者可以专注于解决真正的问题.



  可区分联合作为声明式语言

  可能最简单的例子就是可区分联合可以用来声明行为的规范,例如用来表达和处理数学表达式:

     我们创建了一个用来表述数学表达式的可区分联合.这当然是很原始的"语言",但当你实现处理这些值的函数时,你得到了F#里一个处理数学表达式的语言. 这种技术可以解决的另外一个问题是配置图形用户界面或定义数据操作的模板.

 

  活动模式   

        一个与可区分联合类似的语言特性是活动模式.活动模式可以用来对一些数据类型提供不同的视角,可以使我们隐藏内部表示而只公布这些视角.可区分联合可以对一个值提供不同的视角(Binary, Var,Const).   

        一个类型可以有不同视角的例子是复数,它可以用笛卡尔表达(实部,虚部),也可以用极坐标(绝对值,相位).一旦模块为复数提供这两种视角, 它的内部就可以被隐藏,并且有需要的时候可以修改其实现.   

       建议在公共类库的API里使用活动模式代替可区分联合,这样就可以不破坏现有代码而修改内部实现.另外一个可能的用处是扩展 用可区分联合创建的语言 的"词汇".  

       

        正如你可以看到,活动模式的声明和函数类似,但是用了个奇怪的名字.使用(|PatternName|_|)语法声明一个模式,它可以返回一个成功的匹配或者失败.它有一个参数,匹配成功时返回Some(...) 失败时返回None.   

        活动模式和可区分联合的关键区别是可区分联合用来创建值(意味着它会被所有这个语言的使用者使用),而活动模式用来分解值所以在代码中用来解释这个语言(通常由于语言的设计者写)或者用在预处理或优化代码(由语言的高级用户写).   

       下面我们实现了一个函数用来测试是否两个表达式是相等的,例如10*(a+5) 和 (5+a)*10被当作是相等的:

     

        可以看到用活动模式比明确的使用二元操作符容易得多,并且当需要添加新的交换操作符时只需要修改活动模式就可以了.

  序列解析(sequence comprehensions)   

       在谈的更深之前,我需要插一个关于序列解析的话题.这是一个使我们能够在F#中生成的序列语的言结构:

     

       我们声明了一个列表,然后序列表达式([])选择其中的一些.我们用了 when .. -> 结构,它可以用在过滤和映射操作.它也可以写成下面:  

        

       返回一个值也可以用yield . 这个例子是想演示[]里可以包含很复杂的F#代码:    

           

       上边例子介绍了两个有趣的结构.我们用seq { .. } 生成一个序列,它是延迟执行的.另一个是它是递归的,我们用yield!每次迭代都生成一个序列元素以构成一个大序列,如果用yield则会返回序列,类型就不匹配了.

  F# 计算表达式(Computation Expression)   

        我们将要看到的另一个F#特性可以看作是序列解析的泛化.它允许声明一个类似seq { .. } 却以一种不同的方式执行的代码块. seq 情况下不同是可以用yield返回多个值 .   

        下面我们将创建一个叫maybe 的代码块,它执行一些计算如果成功返回Some(res) ,否则停止执行并立即返回None. 我们首先实现一个要么返回某个值要么返回None的简单函数:   

        

  现在我们可以一段代码,它读取两个数字,并返回和.然而如果readNum 失败,我们想立即停止执行而不再执行另一个readNum .这正是maybe 会做的事:    

        

  先调用printf 然后用let!调用readNum .这个操作称为monadic bind,maybe 块的实现指定了它的行为.   在看maybe的实现之前,我们先看看要实现的函数的类型.所有用monadic builder实现的计算表达式有以下这些初级操作:

        

        我们很快会讨论到F#编译器怎样用这些成员执行计算表达式,在这之前我想给出一些注释. Bind 和Return 指定了标准monadic 操作(懂Haskell的应该熟悉), 我们在代码里用let!的时候用到Bind ,当计算表达式包含return 的时候会调用Return ,最后Delay 允许我们创建延迟执行的monadic .   

       计算表达式是一种可以用类似F#代码的方式写monadic 操作的语法扩展,下面的代码和前面的意思一样:  

       

      可以看到原来的代码被分割成几部分称为monadic 操作的参数,要注意这些表达式可能并不会被计算,这依赖于monadic 操作的行为.   

      我们现在看maybe 的实现吧.Bind会测试如果d是Some(n)则会调用f函数.Result只是简单的对原值进行了封装:

   

     计算表达式更复杂的功能参见文末的参考.

  F#元数据编程( meta-programming)和反射   

        元数据编程意思是像数据一样对待代码并以一种方式处理它. F#里这种技术可以用来翻译F#代码为可以在其它执行环境执行的语言或格式,也可以用来分析计算F#代码的附加属性.   

        F#和.Net运行时的元数据编程能力可以看作两部分. .Net运行时提供了发现所有类型的方法定义的方式,称之为反射. F#引用(quotations )提供了对元数据编程完整支持的第二个部分,它可以用来从反射获取的成员信息提取出一个抽象语法树(注意F#引用是F#编译器的特性,所以C#,VB是不可以的).

  .Net和F#反射   

        F#扩展了.NET System.Reflection 以给出F#数据类型的附加信息,例如可以得到之前定义的Expr的可能值:  

      

      下例演示了F#里特性的语法:    

      

 使用 System.Reflection 类库可以在运行时获取已编译的dll里的特性:  

   

  F# 引用(quotations )   

      F#引用构成了元数据编程的第二部分.有两种办法构成F#引用表达式,用引用符号或者用useReflectedDefinition 特性.:  

       

     下面的例子演示了引用的遍历:     

       

  引用模板和接合(Quotation Templates and Splicing)   

        可以用引用模板对引用进行接合,%是接合运算符:     

           

       接合运算符也可以用来为引用计算提供输入数据,下面的例子基于FLINQ 项目:  

    

  引用顶层定义   

       另一个定义引用的办法是用特性,这种方式有时候被称之为非侵入性的元编程( non-intrusive meta-programming):   

       

  

        使用活动模式和引用  

    活动模式也可以用来实现引用处理器,例如下面声明了一个识别二元操作的活动模式:  

    

  在这个例子里我们声明了一个名为BinaryOp 用来匹配表示加或者减的引用的活动模式.在处理引用,分组的代码里这很有用,因为可以为所有你的翻译器或分析器需要的引用定义活动模式.

  参考  

  [1] Some Details on F# Computation Expressions - Don Syme's WebLog on F# and Other Research Projects   

     [2] Introducing F# Asynchronous Workflows - Don Syme's WebLog on F# and Other Research Projects

  

 

本站技术原创栏目文章均为中睿原创或编译,转载请注明:文章来自中睿,本站保留追究责任的权利。