boo 的 macro(2)

boo 應該是在 0.8 以後吧,就提供了 macro 這個新的關鍵字,用來寫 macro,之前的寫法相當麻煩,需要先繼承 AbstractAstMacro,然後overwrite Expand 這個方法。
新的 macro 關鍵字簡化了一些功夫,macro 之後接的是名稱,下面的 block 就是描述要怎麼去替代,block 的最後再傳回 Ast.Block 即可。
大致的寫法就像這樣子:
[python]
macro Msg:
args=Msg.Arguments # macro 名稱其後加上 .Arguments,表示取得 macro 後面的參數
# [| |] 是相對簡便的語法,表示這裡面是個 Ast.Block,也就是程式區塊,而 .Body 則表示是 macro 下面的 block
# 注意:[| 後與 |] 前一定要分行,否則會有錯誤
return [|
$(Msg.Body)
|]
[/python]

寫法相當簡潔,不過在寫的時候,卻很容易讓人碰壁。最大的原因是用法誨澀,以上面的 Msg macro 來說,當 Msg 123 的時候,Msg.Arguments[0] 的型態照理應該是 Int32 才對,但實際上卻是 Ast.IntegerLiteralExpression,型態已經全然是 Compiler AST tree 裡的型態,macro 裡要取用變數、或產生 block也很容易造成困擾,這對於不玩 compiler 的人來說,是相當高的門檻。再者,文件的缺少也是很重要的因素,官方對於這方面的文件非常缺少(比較少人玩 Boo 也是一個主因)。
但是,這對於創造新的語法來說,卻是相當的便利,這也就是一般常說的 DSL,你可以針對某個特定領域來創造適合的語法。網路上能找到的例子,也多半如此。

boo 的 macro(1)

boo 的 macro 跟 C/C++ 的 macro 很類似,都是在編譯時期就被替代為實際的代碼。不過 C/C++ 只做簡單的替換,boo 的 macro 則是會在編譯時期時進行編譯並且執行、進行替換。

#
# dontimes.boo
#
import System
import Boo.Lang.Compiler
macro DoNTimes:
  n = DoNTimes.Arguments[0] as Ast.IntegerLiteralExpression
  print n.GetType().ToString() # n is IntegerLiteralExpression
  print DoNTimes.Body.GetType().ToString() # DoNTimes.Body is Block
  blocks = Ast.Block() # create new block add DoNTimes.Body n times.
  for i in range(Convert.ToInt32(n.ValueObject)):
    blocks.Add( DoNTimes.Body )
  return blocks

DoNTimes 3:
  print "foo"
  print "Press any key to continue . . . "
  Console.ReadKey(true)

使用 booi 來執行,你會看到下面的訊息,foo 被印了三次:

Boo.Lang.Compiler.Ast.IntegerLiteralExpression
Boo.Lang.Compiler.Ast.Block
foo
foo
foo
Press any key to continue . . .

那這跟用 for 迴圈來跑有什麼不同?
首先用 booc 來編譯:booc -t:exe dontimes.boo,在編譯的時候,你會發現第9行跟第10行的訊息被印了出來:

Boo.Lang.Compiler.Ast.IntegerLiteralExpression
Boo.Lang.Compiler.Ast.Block

這證明了 boo compiler 會在編譯時,把 macro 的部份先拿出來編譯,然後在編譯的時後去執行 macro,對程式碼進行替換。然後你會發現執行 dontimes 的時候,只印了 foo 三次。
reflector 來看,可以看到:

private static void Main(string[] argv)
{
  Console.WriteLine("foo");
  Console.WriteLine("foo");
  Console.WriteLine("foo");
  Console.WriteLine("Press any key to continue . . . ");
  Console.ReadKey(true);
}

代碼只有三行Console.WriteLine(“foo”);,這很清楚的說明了 booc 在編譯時,就把 macro 的內容替換進去了。

booish 與 booc 編譯後的執行結果不同?

Boo Programming Language網上論壇發現了這個討論串:Problems with BooPrimer
發問者表示同樣的程式在 booish 執行與用 booc 編譯後的執行結果不同,我大吃一驚,趕緊試試,發現真的是跟發問者講的一樣,心想完蛋,怎麼會這樣…

i = 0
while i < 5:
print i
i += 1

隔了一天,有人(Stoo)回覆了,說 booish 在執行結束後,會再次印出 i 的值,並建議改成這樣,可以更能看出問題所在:

i = 0
while i < 5:
print "i=${i}"
i += 1

果然,執行結果就如同他回覆所說的一樣:

i = 0
i = 1
i = 2
i = 3
i = 4
5

在 ASP.Net 裡使用 Boo

方法很簡單,只要修改 web.config,然後把 Boo 相關的 assembly 放到 bin 目錄下即可:
[xml]<configuration>
<system.web>
<compilation debug=”true”>
<assemblies>
<add assembly=”Boo.Lang.CodeDom” />
</assemblies>
<compilers>
<compiler language=”Boo” extension=”.boo” type=”Boo.Lang.CodeDom.BooCodeProvider, Boo.Lang.CodeDom” compilerOptions=”-ducky -utf8″/>
</compilers>
</compilation>
<customErrors mode=”Off”/>
</system.web>
</configuration>[/xml]
要注意的是,如果你的應用程式不是 code behind 而且 Hosting 是 IIS 或是 .NET framework 內建的小 web server 時,會有問題。問題出在 Indent,Boo 對於 Indent 很敏感,不知道為甚麼,在 Microsoft.NET 下,Indent 就是會錯。使用Mono XSP的話,則沒有問題。
是故,你可以改使用 code behind 的方式繞過這問題。
會發現這問題,是因為有人在 boolang 討論群組裡問了這問題:boo on asp.net,我去試才知道的。最後提問者改用 xsp…

讓 Banshee 啟動時自動播放音樂之二

每次都從第一首播放,實在太沒意思,所以在播放前切換為 Shuffle 模式,播放時,就會隨機挑選一首開始播放,然後再關閉 Shuffle 模式。

import System
import System.IO
import Banshee.ServiceStack
import Banshee.PlaybackController
def AutoPlay() as bool:
ServiceManager.PlaybackController.ShuffleMode = PlaybackShuffleMode.Song
ServiceManager.PlayerEngine.Play()
ServiceManager.PlaybackController.ShuffleMode = PlaybackShuffleMode.Linear
def OnClientStarted( client as Client ):
Hyena.Log.Information( "engine is playing now..." )
GLib.Timeout.Add(1500, AutoPlay)
Banshee.ServiceStack.Application.ClientStarted -= OnClientStarted
Hyena.Log.Information( "autoplay script is loaded." )
Banshee.ServiceStack.Application.ClientStarted += OnClientStarted

p.s. 上次有提到要作自動記錄播放與自動播放記錄曲目的功能,我的確是做了,只是在播放完指定曲目後,又跳回第一首,這表示我還得繼續研究原始碼才行,所以暫時不釋出。

用 Boo 寫 Web Service

必須先將以 Boo 寫的 Web Service 編譯為 Assembly,然後再製作一個以 c# 或 vb.net 寫的 asmx 繼承該 Web Service 類別才行。
否則會遇到 “The invoked member is not supported in a dynamic module.” 的錯誤。
我是在遇到錯誤的時候,去參考 boo 源碼 examples/asp.net 下的 Math.asmx 與 Math.asmx.boo 才知道這件事情的。
本來我還在納悶,為甚麼 examples/asp.net 下會有一個用 c# 寫的 asmx,還以為是搞錯了呢~

// Math.asmx.boo
// 要先編譯好,放在 bin 目錄下:booc -t:library -out:bin/Math.dll Math.asmx.boo
import System.Web.Services
[WebService]
class Math:
[WebMethod]
def Add(a as int, b as int):
return a+b
[WebMethod]
def Multiply(a as int, b as int):
return a*b
<%@WebService Class="MathService" Language="C#" %>
// Math.asmx
public class MathService : Math  // 繼承用 Boo 寫的 Math 類別
{
}

讓 Banshee 啟動時自動播放音樂

主要是利用上一篇介紹的 BooScript Extension,讓 Banshee 能在一開始就播放音樂…
BooScript Extension 在載入時,會檢查是否有 script 要執行,有的話,會進行編譯並執行,所以這個時候 Banshee 內部還有許多事情還沒初始完成,所以必須要將 AutoPlay 的動作排程到 Application.ClientStarted 裡,讓 Application 啟動之後去執行 AutoPlay 的動作。

import System
import System.IO
import Banshee.ServiceStack
def OnClientStarted( client as Client ):
Hyena.Log.Information( "engine is playing now..." )
ServiceManager.PlayerEngine.Play()
Hyena.Log.Information( "autoplay script is loaded." )
Banshee.ServiceStack.Application.ClientStarted += OnClientStarted

就這麼簡單,下次要加上自動記錄播放與自動播放記錄曲目的功能。

Boo(20)-Generator 函式

Generator 函式其實就跟 C# 的 Iterator 一樣,利用 yield 關鍵字先把值傳回讓呼叫者使用。
使用 Generator/Iterator 最大的好處是可以讓函式只做必要的邏輯,而不需要把一些事情綁在迴圈裡面。
下面就是一個很標準的尋訪目錄樹的範例,尋訪的工作交給 walk,主程式則負責依據傳回的值作處理。

import System
import System.IO
def walk( path as string ):
di = DirectoryInfo( path )
for d in di.GetDirectories():
yield d as FileSystemInfo
for f in di.GetFiles():
yield f as FileSystemInfo
for node in walk( "." ):
if node isa DirectoryInfo:
print "[${node.Name}]"
elif node isa FileInfo:
print node.Name

參考自:Generators
p.s. 這系列文章一定會持續寫到 macro 出現為止。

Boo 的 currying

拜讀了Jserv大的”以 C 語言實做 Functional Language 的 Currying”Thinker大的”真 C 語言實做 Functional Language 的 Currying”以後,決定也來挖掘一下 Boo 的 currying 寫法,根據這篇文章:Boo Programming Language Languages Currying Def Return World,程式碼出乎意料的簡單:
[python]
//Currying:
plusX = { a as int | return { b as int | return a + b }}
print plusX(3)(4)
[/python]
就這樣。老實說,大概懂了,可是又不是很懂,也沒想到用途。
所以,就跟沒懂是一樣的。