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當作計算機 目錄 下一章:字串處理