BOO大全/变数与型别

上一章:把Boo当作计算机 目录 下一章:字串处理


变数与型别 编辑

建立变数的方法很简单,指定值给它们就行了。即使你并没有明确指定变数的型别,私底下,Boo 会根据值来决定。大致上,可以使用的型别有:数值、字串还有阵列...等等。

>>> z = 1.2
(Double) 1.2
>>> 2*z + 1
(Double) 3.4
>>> s = "Hello, World!"
(String) 'Hello, World!'
>>> s = 10
--------^
ERROR: Cannot convert 'System.Int32' to 'System.String'.

一旦变数被宣告了,它就已经是特定的型别,Boo 会在你试图指定其他型别的值时,提出抗议!这意味着,Boo 是一个静态型别的语言﹔它会照看着你并且阻碍你把橘子变成苹果(举例)。那么,该怎么将整数转换为字串? Convert.ToString()?什么进制(译注:上一章有提到)?这时候就该弄清楚了。与Convert.ToString()相对的是 Parse

>>> i =  10
(Int32) 10
>>> i = "2"
--------^
ERROR: Cannot convert 'System.String' to 'System.Int32'.
>>> i = int.Parse("2")
(Int32) 2
>>> int.Parse("mouse")
System.FormatException: Input string was not in a correct format.
...

如果 Parse 失败了,它会提出执行时期的例外。(如果你软件的使用者看到这错误讯息,表示你并没有保护好你的程式。请参见:例外处理)

万一 Boo 无法推断出正确型别或是你想要清楚一点的代码(如:类别里的字段)时,你也可以明确地为变数加上型别宣告。纯粹的宣告可以不需要初始值。

xx as double = 0
s as string

一行只能宣告一个变数。

Duck型别 编辑

译注:原章节为DuckTyping

Boo 主要是个静态型别的语言,跟 C#、C++ 一样。变数在编译时期必须有正确的型别,才能让编译器知道要呼叫哪些方法。这的确很沉闷,不过这确实地让程式快很多,也才不会让你在漫漫长夜里因为"找不到方法"的例外而惊讶。

如果是动态型别,我们不可能知道变数里面的物件到底是什么型别,所以执行时期呼叫物件的方法时,就得去找适当的方法。这通常被称作后期系结,非常有弹性,但是也牺牲了效率与编译时期的检查。像 Python 类的语言,认为弹性与设计上的效率远比执行时期的效率来的重要,同时静态的测试也不利于作广泛的单元测试。

事实上,一个编程语言应该要提供静态与动态的型别,然后让程序员自己去决定要使用哪一种,这会比由语言设计者来决定要来的好。所以 Boo 做了。

duck型别这名词是由 Ruby 社群来的。如果它走起路来像鸭子,叫起来也像鸭子,那么我们可以说,它就是鸭子。最明显的例子就是使用外部物件,像是 COM 物件,它们只运行在执行时期。这边是个 Boo 以 automation 界面呼叫 Internet Explorer 的经典例子:

import System.Threading

def CreateInstance(progid):
   type = System.Type.GetTypeFromProgID(progid)	
   return type()	

ie as duck = CreateInstance("InternetExplorer.Application")
ie.Visible = true
ie.Navigate2("http://www.go-mono.com/monologue/")

Thread.Sleep(50ms) while ie.Busy

document = ie.Document
print("${document.title} is ${document.fileSize} bytes long.")

duck做到了。以duck宣告的变数可以在执行时期接受任何物件。Boo Codehaus 里的文章建议你在读到 duck 时,应该自己要把它想成 any。

同样的例子也可以用 C# 完成,但是会较为棘手。COM Interop 在 Windows 世界里仍然是个重要的技术;所有的 Microsoft Office 应用程序仍提供了丰富的 automation 界面。

目前 Boo 仍不支援泛型 (现在已经支援了,作者写这篇时的确还没提供)。但你还是可以利用 dock 型别做出类似 Python 风格的泛型程式,你可以参考后面的以duck型别作泛型

duck 型别并不只限于在 Boo 内使用,如果你的组件有使用到 duck 型别,在其他 .NET 语言里仍然可以使用。这个为 duck 型别寻找适当方法的神奇功能由 Boo.Lang.dll提供,目前所有 Boo 的应用程序仍然需要这个组件。(译注:作者有提出 BooWithoutBooLang,但是没有内容,我猜是利用 ILMerge 之类的工具将组件作合并。)

类别可以使用 IQuackFu 界面取得完整的方法配送控制。

IQuackFu 编辑

有时候如果能控制方法配送机制的话,会非常有用。任何实作 IQuackFu 界面的类别都必需要作三件事情:如何呼叫方法、如何设定属性与如何读取属性。看看下边的例子,这个类别包裹了一个杂凑表并提供了类似 Javascript 的存取方式。所以使用 e.Alice 就跟呼叫 e['Alice'] 是一样的。

class Map(IQuackFu):
  _vars = {}
	
  #implement the IQuackFu interface
  def QuackSet(name as string, value):
    _vars[name] = value
		
  def QuackGet(name as string):
    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

以duck型别作泛型 编辑

译注:原章节为GenericProgramming。

以加总序列然后传回元素个数与加总来作为泛型的程序再适合不过了。我们可以使用duck型别来当作引数与传回型别,这样就适用于任何序列与任何可以被转换为 double 的型别。

def sum(seq as duck):
  y = 0.0
  k = 0
  for x as duck in seq:
    y += x
    ++k
  return k,y
import Tokens
	
print "array sum is ",sum((2.3,4.2,5.5))[1]
print "integers ",sum((10,20,30,40,50))[1]
y = sum(Tokenizer("numbers.txt").Numbers())
print "num ",y[0],"sum is ",y[1]

我们可以让它更好,下面这个版本将适用于任何支援"加"的型别:

def sum(seq as duck):
  y as duck
  k = 0
  for x as duck in seq:
    if k == 0:
      y = x
    else:
      y += x
    ++k
    return k,y

上一章:把Boo当作计算机 目录 下一章:字串处理