循序渐进学Boo - 知识篇

Boo官方网站上提供了详细的介绍与实例(尽管有小部分内容现已过时,但大家还是可以学习到Boo的语法特性):
1.Boo Primer
2.Language Guide
如果大家希望能深入了解到Boo的语法特性,强烈建议大家阅读Steve Donovan写的The Book of Boo - A TiddlyWiki about Boo

在大家阅读完上述链接的内容后,本文着重结合"从一个小实例开始 - Boo开篇"中的实例向大家介绍Boo相对重要的特性,为后续文章的阅读奠定基础。

首先要强调的是,编写Boo代码需要遵循以下的代码结构顺序:
module docstring
namespace declaration
import statements
module members: class/enum/def declarations
main code executed when script is run
assembly attributes
下面是个例子:

""" module docstring """

namespace My.NameSpace # optional namespace declaration



import System # import statements

# followed by the Members of this module (classes, methods, etc.)

class Heater:

    [Getter(Manufacturer)]

    _manufacturer = "Siemens"

    [Property(Temperature, Temperature > 0)]

    _temperature as int

 

    def Boil():

        pass # 如果不想有具体实现,可以使用关键字pass



# start main section that is executed when script is run

heater = Heater()

heater.Temperature = 96



# optional assembly attribute declarations used when compiling

[assembly: AssemblyTitle('Title')]

[assembly: AssemblyDescription('Description')]

一、Classes

正如上面例子看到在Boo中很容易进行类的定义与实现。在Boo的类定义中,可以很容易使用Property、Getter、Setter这三个attributes进行字段的属性设置。
其中Property还提供了第二个参数,可以用它指定Pre-condition,只有符合这个条件时才可以进行setter的操作:

[Property(Temperature, value > 0)]
_temperature as int

此外之外,我们也可以使用自定义的attribute来进行扩展(通过AbstractAstAttribute)。

类的默认修饰符是public,类中所有的字段默认都是 protected,所有的方法、属性和事件默认都是 public。

Boo 提供了一个非常有用的特性,它让你可以在实例化对象时直接指定属性的值。
heater = Heater(Temperature: 95, Manufacturer: 'Philips')

在Boo中继承同样很简单:

abstract class Device:

    abstract def Do():

        pass



class Alarm(Device):

    override def Do():

        print "Alarm!"

二、AbstractAstAttribute

其命名的惯例是:"属性名称"+Attribute。
下面是一个简单的自定义属性(AbstractAstAttribute)例子,它继承自Boo的PropertyAttribute(继承于AbstractAstAttribute):

import Boo.Lang.Compiler

import Boo.Lang.Compiler.Ast

 

class NonEmptyStringAttribute (PropertyAttribute):

    static preCondition = [| value is not null and len(value) > 0 |]

 

    def constructor(propName as ReferenceExpression):

        super(propName,preCondition.CloneNode())



class Person:

    [NonEmptyString(Name)]  # line 4

    _name as string

 

    [property(Age)]

    _age as int

 

    override def ToString():

        return _name

 

p = Person(Name:"john",Age:36)

p.Name = ''   # line 14

print p

相比复杂的函数调用,AST literals则提供了一个更简洁、更友好的方式用以构建Boo Ast nodes。

// using constructor calls.

mie = MethodInvocationExpression(ReferenceExpression('print'), 

   StringLiteralExpression('Hello,world'))



// ast literals allow you to say:

mie = [| print "Hello,world" |]

也就是说,你可以直接从Boo代码中构建Ast对象。

【注】:这里[| print "Hello,world" |]被称为Quasi-Quotation。

下面是详细的英文解释:A quasi-quotation evaluates its body as a code tree expression.
This is a special type of operator that tells Boo that whatever is inside of the block is to be parsed and returned as an AST(abstract syntax tree) node.

$ is generally called the "splice" operator and it means "evalute me at compilation time".
The splice application $(condition.ToCodeString()) automatically lifts the string value returned by ToCodeString to a proper StringLiteralExpression.
 

下面是前一篇文章中使用AbstractAstAttribute的例子:

import System

import Boo.Lang.Compiler

import Boo.Lang.Compiler.Ast



# AST Attributes inherit from AbstractAstAttribute

class EnsureAttribute(AbstractAstAttribute): 

 # The expression that we were supplied

 # note that this happens during compilation

 expr as Expression

 # Store the expression in a field

 def constructor(expr as Expression):

  self.expr = expr  



 # Now we get the chance to make the changes that we want 

 def Apply(target as Node):

  # we cast the target to a ClassDefinition and 

  # start iterating over all its members

  type as ClassDefinition = target

  for member in type.Members:

   method = member as Method

   continue if method is null

   # if the member is a method, we modify

   # it to include a try/ensure block, in which 

   # we assert that the expression that we got must

   # be true. Then we override the method body with 

   # this new implementation.

   block = method.Body

   method.Body = [|

    block:

     try:

      $block

     ensure:

      assert $expr

   |].Body



# 使用方法如下: 

[Ensure(name is not null)]

class Customer:



 name as string



 def constructor(name as string):

  self.name = name



 def SetName(newName as string):

  name = newName

【注】:
Boo AstAttributes explained有详细的描述,对大家的理解会有很大帮助。

三、AbstractAstMacro

Boo中的Macros是在编译过程阶段被编译器扩展,并根据相应的参数和代码转换为AST以实现Boo编译器扩展的能力,因此通常用Macros来创建你自己的关键字。

在 Boo 里的某些述句,像 print 和 using,其实都是通过Macro来实现的。Macro 可以接受参数Arguments,也可以拥有代码块Block。其语法如下:
SomeMacro arg1, arg2, ...:
  block

值得注意的是,收到的参数并不能直接Evaluate使用,它们会被编译为 AST (Abstract Syntax Tree)表达式以进行操作,也就是说,参数并没有真正被赋值(evaluate)。
Macro 通常用来产生代码,它们会在编译时期以真正的代码取代。

如同自定义属性(AbstractAstAttribute)一样,Macro的名称必须以 'Macro' 结尾。
要使用自定义Macro,你遇到最大的问题在于必须对 AST有相当程度的了解,要建立运算式和述句,你需要了解编译器如何表现它们。
由于Macro正是Boo的优势所在,随后我会再写一篇文章希望可以详细介绍它。

在上篇文章最后部分我给出了大师的代码,其中unroll和unroll2就是分别用不同的方法实现Macro(其实本质是一样的):

// Method 1

import System

import Boo.Lang

import Boo.Lang.Compiler

import Boo.Lang.Compiler.Ast



# We create a class for the macro, the class name is

# meaningful, [macro name]Macro allows us to later refer

# to the macro using just [macro name].

# Note that we inherit from AbstractAstMacro

class UnrollMacro(AbstractAstMacro):

 # Here we perform the actual compiler manipulation

 # the compiler hands us a macro statement, and we have

 # to return a statement back, which will replace it.

 def Expand(macro as MacroStatement) as Statement:

 

  # define a block of code

  block = Block()

  

  # extract the second parameter value

  end = cast(IntegerLiteralExpression, macro.Arguments[1]).Value

  

  for i in range(end):

   # create assignment statement, using the block: trick and add it to 

   # the output

   statements = [|

    block:

     $(macro.Arguments[0]) = $i

   |].Body

   block.Add(statements)

   

   # add the original contents of the macro

   # to the output

   block.Add(macro.Body)

   

  return block



// Method 2

import System

import Boo.Lang

import Boo.Lang.Compiler

import Boo.Lang.Compiler.Ast



# Using the MacroMacro, we don't need a class,

# just to define what we want the macro to do

macro Unroll2:

 # define a block of code

 block = Block()

 

 # extract the second parameter value

 end = cast(IntegerLiteralExpression, Unroll2.Arguments[1]).Value

 

 for i in range(end):

  # create assignment statement, using the block: trick and add it to 

  # the output

  statements = [|

   block:

    $(Unroll2.Arguments[0]) = $i

  |].Body

  block.Add(statements)

  

  # add the original contents of the macro

  # to the output

  block.Add(Unroll2.Body)

  

 return block



# 使用方法如下: 

unroll i, 5:

 print i

 

unroll2 i, 5:

 print i

最后大家看看下面这个简单的Macro,看看它是做什么用的:

import System

import Boo.Lang.Compiler

import Boo.Lang.Compiler.Ast



class AssignFieldsMacro(AbstractAstMacro): 

    override def Expand(macro as MacroStatement):  

        ctor = macro.GetAncestor(NodeType.Constructor) as Constructor

        b = Block()

  

        for param in ctor.Parameters: 

            assign = BinaryExpression(BinaryOperatorType.Assign,

                ReferenceExpression("_" + param.Name),

                ReferenceExpression(param.Name))

            b.Add(ExpressionStatement(assign))

  

        return b

】:
Boo AstMacros explained里有详细描述。

四、Meta Method

如果你不需要操作AST nodes,那么可以使用meta method代替Macros。

import System

import Boo.Lang.Compiler.Ast



class MetaMethods:

    [meta]

    static def verify(expr as Expression) as UnlessStatement:

        return [|

            unless $expr:

                raise $(expr.ToCodeString())

           |]

当编译这段Boo代码发现了verify关键字时,这个方法会通知编译器用下面
[|
unless $expr:
  raise $(expr.ToCodeString())
|]
生成的实际代码来替换。

五、Extension Method

可以用来往已有的类型中添加新的方法,类似于C#中的Extension Method。请看前面的例子:

class MyExtensions:

# Creating an extension methods so 200.ms will be valid

[Extension] 

static ms[i as int]:

    get:

        return TimeSpan.FromMilliseconds(i)



# 使用方法:

limitedTo 200.ms:

    ......

六、IQuackFu

有时候如果能让类自己负责方法、属性的调用,将会非常有用。任何实现了 IQuackFu 接口的类都必须做到:
1.如何调用方法、2.如何设定属性、3.如何读取属性。
在Steve Donovan写的例子中,通过IQuackFu接口实现了类似JavaScript中对象的存取方式:

class Map(IQuackFu):

    _vars = {}  

    def QuackSet(name as string, parameters as (object), value as object) as object:

        _vars[name] = value 

    def QuackGet(name as string, parameters as (object)) as object:

        return _vars[name] 

    def QuackInvoke(name as string, args as (object)) as object:

        pass

 

e = Map()

e.Alice = 32

e.Fred = 22

e.John = 42

print e.Alice,e.Fred,e.John

还有前文中的XmlObject,它是实现了IQuackFu接口的类,因此可以根据节点名称找到相应的值:

class XmlObject(IQuackFu): # Implementing IQuakcFu interface

 

 _element as XmlElement   # The element field

 

 # Get the xml element in the constructor and store it a field

 def constructor(element as XmlElement):

  _element = element   

 

 # We do not support invoking methods, so we just ignore this

 # method. We could also raise NotSupportedException here.

 def QuackInvoke(name as string, args as (object)) as object:

  pass # ignored

 

 # If we wanted two way communication, we could have built

 # it into this method, but we aren't, so we are ignoring this 

 # method as well

 def QuackSet(name as string, parameters as (object), value) as object:

  pass # ignored  

 

 # Here we intercept any property call made to the object.

 # This allows us to translate a property call to navigating

 # the XML tree.

 def QuackGet(name as string, parameters as (object)) as object:

 # Get the node(s) by its name

  elements = _element.SelectNodes(name)

  if elements is not null: # Check that the node exists

  # Here we are being crafy, if there is only one node

  # selected, we will wrap it in a new XmlObject and 

  # return it, this allows us easy access throughout

  # the DOM.

   return XmlObject(elements[0]) if elements.Count == 1

  # If there is more than one, we are using a generator

  # in order to return an enumerator over all the nodes

  # that were matched, wrapping each of them in turn.

   return XmlObject(e) for e as XmlElement in elements 

  else:

   return null



 # This is to make it easier to work with the node 

 override def ToString():

  return _element.InnerText



# 它的调用方法如下:

print "Using XML OBject"

print " - - - - - - - - - "

xml = """

<People>

 <Person>

  <FirstName>John</FirstName>

 </Person>

 <Person>

  <FirstName>Jane</FirstName>

 </Person>

</People>

"""



xmlDocument = XmlDocument()

xmlDocument.LoadXml(xml)



doc = XmlObject(xmlDocument.DocumentElement)

for person as XmlObject in doc.Person:

 print person.FirstName

七、Generators

Boo支持下列三种Generators:
1. Generator Expressions
语法:<expression> for <declarations> [as <type>] in <iterator> [if|unless <condition>]

Generator Expression作为一个enumerator对象使用,所以可以作为函数返回值、函数参数以及赋值给一个变量:

// 作为函数返回值

def GetCompletedTasks():

    return t for t in _tasks if t.IsCompleted



// 作为变量值

i = 2

a = range(1,5)

generator = i*j for j in a

print(join(generator)) # prints "2 4 6 8"

2. Generator Methods
Generator方法可以让你有能力自己实现类似Generator Expression的效果,通过在方法中使用yield关键字来建立Generator方法。
例如:

# 你可以自定义实现irange函数(与系统提供的range相似)

def irange(begin as int, end as int, step as int):

    t = begin

    while t < end:

        yield t

        t += step



for t in irange(1,5,1):

    print t

def selectElements(element as XmlElement, tagName as string):

   for node as XmlNode in element.ChildNodes:

      if node isa XmlElement and tagName == node.Name:

         yield node

3. List Generators
提供快速创建List的方法,它有以下多种定义方式:
[element for item in enumerable]
[element for item in enumerable if condition]
[element for item in enumerable unless condition]

List(x**2 for x in range(5))

List(x+2 for x in range(5) if x % 2 == 0)

八、匿名函数/Closures

在Boo中有两种使用Closure的方式:
1、Block based syntax
heater.Notify += def(heater as Heater):
    print heater.Temperature

2、Braces based
heater.Notify +=  {heater as Heater| print heater.Temperature}

a = 0 # declare a new variable

getter = { return a }

setter = { value | a = value }



assert 0 == getter()

setter(42)

assert 42 == getter()

assert 42 == a

【注】:
1、在Boo有这样的语法特性:如果方法参数中某个参数是callable的函数,那么利用上述匿名函数的写法。
例如:
Boo内置函数map将IEnumerable中的每个元素进行指定函数的转换,然后再将结果放入一个新的IEnumerable对象回传回来。

def sqr(x as double):

    return x*x

iter = map(range(0,10),sqr)

print join(iter)



# 根据刚才的介绍我们也可以有如下更lambda方式的调用:

iter1 = map(range(0,10)) def(x as double):

    return x*x

iter2 = map(range(0,10),{x as double | x*x}) 

2、不定参数
在函数参数args前加*表示参数的数目将是不固定的,类似C#中的params。

// *args表示参数不固定,(object)是类似C#中object[]的表示方法

def Test(*args as (object)):

 return args.Length



print Test(1,2,3,4)

a = (2,3)

// 注意下面两种调用方式的不同

print Test(a)  // 1 

print Test(*a) // 2



// Currying 

// Boo

plusX = { a as int | return { b as int | return a + b }}  

print plusX(3)(4)

// C#

Func<int,Func<int, int>> plusX = delegate(int x) { return delegate(int y) {return x+y; }; };

Console.WriteLine(plusX(3)(4));

这些特性在我们用Boo来实现DSL中会十分的常见。

九、callable/ICallable

前面我们提到可以将函数赋值给变量,但是这个变量是什么类型呢?我们经常需要把函数当作一个参数传给另一个函数使用。
在Boo中,关键字callable就是将函数作为类型使用(函数指针),与C#中的delegate作用类似。
大家可以在前一篇文章“用Boo实现的Observer”中看到下面两者的含义是一样:

// Boo

callable NotifyEventHandler(heater as Heater)

// C#

public delegate void NotifyEventHandler(Heater heater);

ICallable interface
通常 Boo 允许你调用callable的类型,因此将自己的类实现ICallable接口,你也可以让你的类成为被调用的callable类型。

请看下面的例子:

class Fred(ICallable):

    def Call(args as (object)) as object: 

        return (args[0] as string) + (args[1] as string)

 

f = Fred()

assert f('one','two') == 'onetwo'

参考:

http://tore.vestues.no/category/boo/
http://zh.wikibooks.org/w/index.php?title=BOO/BooFunctions&variant=zh-cn

敬请期待:

循序渐进学Boo - 高级篇
循序渐进学Boo - DSL篇

【附上】 
前一阵看到老赵提到的“从CSDN的趣味题学Python”这篇文章,我也用Boo实现了一下(其实与Python极其类似,呵呵,照着改写就好了),权当做熟悉Boo语法。
第一题:

dic = {}

for s in "abcdab121":

    if s not in dic:

        dic[s] = 1

    else:

        dic[s] = cast(int,dic[s]) + 1

for n in dic.Keys:

    print "${n}:${dic[n]}"

第二题:

txt = "床前明月光,疑似地上霜,举头望明月,低头思故乡。"

offset = 6

b = reversed(i) for i in zip(*array(List(txt[i:i+offset]) for i in range(0,len(txt),offset)))

for i in b:

    print join(i,'|')

从这里大家可以看到Boo和Python在语法特性上十分相似。

你可能感兴趣的:(OO)