zoukankan      html  css  js  c++  java
  • 循序渐进学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在语法特性上十分相似。

  • 相关阅读:
    机器学习
    Python
    sublime的推荐插件
    C语言编程
    将生成logo图片导入到Altium Designer中
    基于MDK的stm32实践过程中,debug的总结
    LCD12864使用总结
    c语言使用技巧
    LCD12864显示中文乱码
    在Keil中做stm32的软件仿真,查看输出PWM波形时,在逻辑分析仪中规定IO口signal,出现"unknow signal"
  • 原文地址:https://www.cnblogs.com/huyh/p/1444048.html
Copyright © 2011-2022 走看看