最近几年中,得益于 Java 和.NET 平台良好的可扩展性,在这两个平台上都出现了一大批令人激动的新语言。在 Java 领域内,人们欣喜地看到了 JRuby 和 Groovy 这两门语言的出现,它们均在语法、动作(Action)方面提供了很高程度的灵活性。若合理使用的话,会大大提高开发者的生产效率。.NET 平台上也确实出现了不少创造性的新语言,微软公司官方支持的 IronPython 和 F#都让人们对 CLI 的灵活性和可扩展性充满了信心。
在 IronPython 和 F#受到广泛关注和支持的同时,其他一些同样基于 CLI 的语言也在默默地争取着自己的生存空间。例如开源社区所推崇的 L#(一门 CLI 上运行的基于 Lisp 的语言)、IronRuby 和 Ruby.NET 等两个 Ruby 的实现等。
很多这类创新都是将现有的语言移植到 CLI 平台上(例如,IronPython 就是 CLI 上的 Python 语言,就像 JRuby 是 JVM 上的 Ruby 语言一样),但也出现了一些全新的、拥有自己独特语法的语言,虽然这些全新的语言也难免受到目前如日中天的各种流行语言的影响。boo 就是其中之一。boo 是 CLI 平台上的一种静态类型的语言,其很多特性都受到了 Python 的影响,但却又不是 Python 的简单移植。实际上,boo 并不在意代码的缩进,也不强迫我们使用 self 关键字。另外,boo 从根本上来讲还是一种静态类型语言,这也与 Python 的动态特性不尽相同。
借助于 boo 所内建的语言特性以及简单的语法结构,加上其静态特性,我们可以用该语言更加高效地编写.NET 应用程序,程序的执行效率甚至也能与 C#不相上下。另外,在 boo 中,我们还可以使用任何 CLI 平台上的现存类库,boo 代码同样能够容易地在其他 CLI 语言中被重用!
使用 boo 开发非常简单
Rodrigo B. de Oliveira 曾经对 C#的很多过于严格的编码规则(例如类型的强制转换)及其不能通过 shell 测试运行代码感到非常郁闷。于是 boo 语言应运而生,并很快发展成一个非常方便的.NET 和 Mono 通用的平台,基于该平台,我们能够创建 GUI 原型、单元测试甚至游戏等各类程序。
学习 boo 的最简单方法就是从 boo 的交互 shell 开始,boo 的交互 shell 又名 booish。在 booish 中,我们即可灵活地任意察看代码片断,进而理解 boo 的语法规则。
对于最常见的 hello world 程序来讲,boo 的视线非常简单——print 加上将要输出的字符串即可。
>>> print "Hello Scary World!"<br></br>Hello Scary World!
需要说明的是,我们还可以将上述代码保存在代码文件中,然后使用booi命令执行该源文件。
C:\dev\projects\acme>booi src\helloworld.boo<br></br>Hello scary world!
我们还可以用booc命令将helloworld.boo脚本编译成为 Windows 可执行文件,即将 boo 代码编译成合法的 CLI 应用程序。
C:\dev\projects\acmi>booc -out:bin/helloworld.exe src/helloworld.boo<br></br>Boo Compiler version 0.7.6.2237 (CLR v1.1.4322.2032)<br></br>C:\dev\projects\acme\bin>helloworld.exe<br></br>Hello scary world!
boo 是一种静态类型的语言,每个变量都有自己的类型。例如,我们可以创建一个值为“Hello World”的 string 类型变量:
>>> var = "Hello world"<br></br>'Hello world'<br></br>>>> var.GetType()<br></br>System.String
注意到这里并没有显式地将 var 声明为 string 类型,但由于 boo 的静态特性并根据该变量被设置的值,var 就自动地被设置成了 string 类型。
boo 字符串
与 Python 类似,boo 为字符串、集合等类型添加了很多灵活的内建(built-in)功能支持,让我们举例说明。
boo 支持字符串改写(使用 ${}语法),还支持 Python 风格的多行字符串。
>>> adj = "scary"<br></br>'scary'<br></br>>>> print "Hello ${adj} world"<br></br>Hello scary world
多行字符串非常易于使用,更不用担心字符转义等问题。
firstname = "Joe"<br></br>lastname = "Dirt"<br></br>accountid = "A499"<br></br>amount = "1.32"<p> msg = """</p><br></br><person fname="${firstname}" lname="${lastname}"></person><br></br><account amount="${amount}" id="${accountid}"></account><p>"""</p>
与 Python 和 Ruby 类似,正则表达式和集合类型(例如 list 和 map)的支持内建到了 boo 语言本身中,并提供了语法上的快捷使用方式。
正则表达式通过“/”定义,其实际的类型为System.Text.RegularExpressions.Regex。匹配则通过 Perl 风格的“=~”操作符完成。例如,如下代码即可用来匹配邮政编码信息:
>>> regx = /^\d{5}([-]\d{4})?$/<br></br>^\d{5}([-]\d{4})?$<br></br>>>> if '22101-4444' =~ regx:<br></br> print "yes"
### boo 的集合类型
boo 支持三种内建的集合类型:普通数组(长度固定,且只能包含某一确定类型的数据)、列表(长度可变,能包含不同类型的数据)以及哈希表(用来存储名称 / 值对)。
数组
数组用来保存一系列相同类型的对象,不能超过某个预定的长度。在 boo 中的创建语法如下:
>>> myarray = (1,2,4,5)<br></br>(1, 2, 4, 5)
我们也不能在数组中添加不同类型的数据。
>>> myarray.SetValue(6, 3)<br></br>>>> myarray<br></br>(1, 2, 4, 6)<br></br>>>> myarray.SetValue('test', 3)<br></br>System.InvalidCastException: Object cannot be stored in an array of this type.<br></br> at System.Array.InternalSetValue(Object value, Int32 index1, Int32 index2, Int32 index3)<br></br> at Input50Module.Main(String[] argv)
### 列表
列表是一类长度不固定的、可被索引且能够包含多种不同类型的数组。这是一种非常灵活的类型,通过从 Python 中借来的方括号([])创建,属于Boo.Lang.List类型。
>>> mylist = [1, 2, 3, 4]<br></br>[1, 2, 3, 4]<br></br>>>> mylist.GetType()<br></br>Boo.Lang.List<br></br>>>> mylist.Add('test')<br></br>[1, 2, 3, 4, 'test']
可以看到,boo 中的列表能够包含不同类型的对象,还能够使用 Add 方法添加新元素。若想了解列表目前包含了什么,我们可以使用 Contains 方法,甚至还能够通过询问逻辑问题实现:
>>> mylist.Contains(3)<br></br>true<br></br>>>> 'test' in mylist<br></br>true
### 哈希表
boo 中的哈希表是一个保存名称 / 值对的容器。这些名称 / 值对均可为不同的类型。
>>> myhash = {".NET":"boo", "Java":"Groovy"}<br></br>{'Java': 'Groovy', '.NET': 'boo'}
哈希表通过名称 / 值对实现,因此,若是输入了名称,那么将得到其值。在下面的代码中,我们输入了".NET",得到了’boo’。
>>> myhash[".NET"]<br></br>'boo'
我们还可以使用 ContainsKey 或 ContainsValue 方法搜索哈希表。
>>> myhash.ContainsKey('Java')<br></br>true<br></br>>>> myhash.ContainsValue('Groovy')<br></br>true
迭代
与 Python 和 Ruby 类似,boo 也允许我们容易地对集合类型进行迭代。不过 boo 不支持 Ruby 或 Groovy 中常见的块(block)和闭包(closure)。
列表的迭代
通常情况下,boo 中的迭代是使用 for 语句实现的:
>>> mylist<br></br>[1, 2, 3, 4, 'test']<br></br>>>> for value in mylist:<br></br>... print value<br></br>...<br></br>1<br></br>2<br></br>3<br></br>4<br></br>test<br></br>>>> myint<br></br>9<br></br>>>> myarray<br></br>(1, 2, 4, 6)<br></br>>>> for i in myarray:<br></br>... print 2*i<br></br>...<br></br>2<br></br>4<br></br>8<br></br>12
### 哈希表的迭代
我们也可以在哈希表上进行迭代:
>>> myhash<br></br>{'Java': 'Groovy', '.NET': 'boo'}<br></br>>>> for key in myhash:<br></br>... print key.Value<br></br>...<br></br>Groovy<br></br>boo
需要注意的是,因为 boo 的哈希条目存贮在System.Collections.DictionaryEntry类型中,所以我们可以根据需要访问其 Key 和 Value 成员。
>>> for key in myhash:<br></br>... print key.Key<br></br>...<br></br>Java<br></br>.NET
boo 中的函数
boo 允许我们在类定义之外创建函数,这一点非常类似于 Python 和 Groovy。在 boo 中,函数是“一等公民”(即函数本身也是对象),使用def关键字创建。
例如,如下代码定义了一个值为’boo’的字符串。需要注意的是对于字符串类型,boo 中没有 Python 那样内建的 lower 方法。但创建一个 lower 方法却相当的简单:使用 def 关键字定义,并通过“as ”语句指定参数类型即可。
>>> str = "Boo"<br></br>'Boo'<br></br>>>> str.GetType()<br></br>>>> lower(str)<br></br>----^<br></br>ERROR: Unknown identifier: 'lower'.<br></br>>>> def lower(val as string):<br></br>... return val.ToLower()<br></br>...<br></br>>>> print lower(str)<br></br>boo
boo 中的 IO
通过使用 using 关键字,boo 让文件操作变得非常简单。我们根本不用担心文件处理中的一些常见问题,例如关闭文件等,因为 using 语句将自动为我们做好这些。
例如,读写某个本地文件将非常容易:
>>> import System.IO<br></br>>>> myfile = "SampleFile.txt"<br></br>'SampleFile.txt'<br></br>>>> using input = File.OpenText(myfile):<br></br>... for line in input:<br></br>... print line<br></br>...<br></br>Welcome to an easy<br></br>way to read files<br></br>using the most unscary language around:<br></br>BOO!
通过使用函数,我们可以重新创建 Groovy 中 getText 风格的方法:
>>> import System.IO<br></br>>>> def GetText(file as string):<br></br>... retString = " "<br></br>... using input = File.OpenText(file):<br></br>... for line in input:<br></br>... retString += line<br></br>... return retString<br></br>...<br></br>>>> myfile = "SampleFile.txt"<br></br>'SampleFile.txt'<br></br>>>> assert GetText(myfile).Equals('Welcome to an easy way to read files using the most unscary language around: BOO! ')<br></br>>>>
与 NAnt 集成
通过 boo 任务,boo 能够与 NAnt 编译文件协同工作。下面这个例子就演示了 boo 与 NAnt 协同工作能够完成的任务——在某个 dll 上应用 FxCop,然后用 boo 将其中Critical Errors的个数统计出来:
<property name="fxcop.xml"></property><property name="fxcop.xml"></property> value="${build.dir}\bin\${build.config}\fxcop.xml"/><br></br><target depends="build" name="fxcop"></target><br></br><fxcop></fxcop><br></br><targets></targets><br></br><include name="${build.dir}\bin\${build.config}\${library}"></include><br></br><boo></boo><br></br> import System.IO<br></br> fpath = Project.Properties['fxcop.xml']<br></br> numerror = 0<br></br> using input = File.OpenText(fpath):<br></br> for line in input:<br></br> if line =~ /Level="CriticalError"/:<br></br> numerror++<p> print("There were ${numerror} Critical Errors")</p>
在 boo 中单元测试非常简单
因为 boo 能够与其他 CLI 库共同使用,所以 boo 代码也能够容易地使用 NUnit 的属性。
例如,如下代码使用了用来测试数据库的 NDbUnit,并用 NUnit 创建了一个简单的测试用例。在该测试用例中,我们使用 NDbUnit 的 API 插入一些数据,然后确认这些数据确实保存到了数据库中。
可以看到,与 C#相比,boo 代码显得简洁许多。因为 boo 并不使用分号和大括号,且类型声明也少了很多。
import NUnit.Framework<br></br>import NDbUnit.Core.OleDb<br></br>import NDbUnit.Core<br></br>import System.Data<br></br>import System<br></br>import System.Data.OleDb<p> [TestFixture]</p><br></br>class WordTest:<p> final CONN = "Provider=SQLOLEDB...."</p><br></br> final SCHEMA = "Dataset2.xsd"<br></br> final XML = "XMLFile2.xml"<br></br> fixture as OleDbUnitTest<p> [SetUp]</p><br></br> def configure():<br></br> fixture = OleDbUnitTest(CONN)<br></br> fixture.ReadXmlSchema(SCHEMA)<br></br> fixture.ReadXml(XML)<p> [Test]</p><br></br> def VerifyWordTableOle():<p> fixture.PerformDbOperation(DbOperationFlag.CleanInsert)</p><br></br> select = "select spelling from word where word.word_id = 2"<p> adapter = OleDbDataAdapter(select , CONN)</p><p> dta = DataSet()</p><br></br> adapter.Fill(dta, "word")<br></br> table = dta.Tables["word"]<p> for row as DataRow in table.Rows:</p><br></br> Assert.AreEqual("pugnacious", row[0],<br></br> "word spelling wasn't pugnacious")
若你想以最快的速度开发单元测试,那么 boo 将是一个明智的选择。因为 Boo 支持属性(IronPython 并不支持),因此与 NUnit 配合起来也没有任何问题。
静态却不失动态特性
虽然我们并不需要显式声明变量的类型,但 boo 却的的确确是一种静态类型的语言。boo 在底层根据变量的值来自动决定变量的类型。例如,如下一段脚本创建了一个 string 类型,并尝试调用一个并不存在的方法。
var = "BOO"<br></br>var.lower()
尝试编译该脚本,将得到如下错误:
C:\dev\projects\acme>booc -out:bin/statictypes.exe src/statictypes.boo<br></br>Boo Compiler version 0.7.6.2237 (CLR v1.1.4322.2032)<br></br>src/ statictypes.boo(3,5): BCE0019: 'lower' is not a member of 'string'.<br></br>1 error(s).
若是直接运行该脚本,也将得到同样的错误:
C:\dev\projects\acme>booi src\statictypes.boo<br></br>src\statictypes.boo(3,5): BCE0019: Boo.Lang.Compiler.CompilerError: 'lower' is not a member of 'string'.
尽管如此,boo 在类型方面仍提供了很多便利。通过使用duck类型,我们可以推迟编译期类型检查。若某个变量的类型为 duck,boo 将尝试用反射的方式调用方法。例如,若我们将 var 声明为duck类型,那么将不会得到编译期错误。而若是直接运行这段脚本,将得到一个不同的错误:
C:\dev\projects\acme>booi src\statictypes.boo<br></br>System.MissingMethodException: Method System.String.lower not found.<br></br> at System.RuntimeType.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Obje<br></br>ct[] args, ParameterModifier[] modifiers, CultureInfo culture, String[] namedParameters)<br></br> at System.Type.InvokeMember(String name, BindingFlags invokeAttr, Binder binder, Object target, Object[] ar<br></br>gs)<br></br> at Boo.Lang.Runtime.RuntimeServices.Invoke(Object target, String name, Object[] args)<br></br> at StatictypesModule.Main(String[] argv)
借助于 duck 类型,boo 能够以非常优雅的方式编写测试程序并控制 Internet Explorer:
>>> ie as duck =<br></br> System.Type.GetTypeFromProgID("InternetExplorer.Application")()<br></br>System.__ComObject<br></br>>>> ie.Visible = true<br></br>true<br></br>>>> ie.Navigate2("http://www.thediscoblog.com")<br></br>>>> links = ie.Document.getElementsByTagName("a")<br></br>System.__ComObject<br></br>>>> site = "http://thediscoblog.com/2007/02/04/currying-maximum-favor-with-groovy/"<br></br>'http://thediscoblog.com/2007/02/04/currying-maximum-favor-with-groovy/'<br></br>>>> for link in links:<br></br>... if(link.href.Equals(site)):<br></br>... link.click()<br></br>...<br></br>mshtml.HTMLAnchorElementClass
注意到变量ie的类型为 duck,这样即可更加优雅地将执行消息传递给该实例,而并不会导致任何错误。
找到页面上的某个链接之后,我们可以点击该链接,然后精确验证随后页面中的内容。
>>> h3s = ie.Document.getElementsByTagName("h3")<br></br>System.__ComObject<br></br>>>> for h3 in h3s:<br></br>... if(h3.id.Equals("comments")):<br></br>... assert h3.innerText =~ "5 Responses"<br></br>...<br></br>mshtml.HTMLHeaderElementClass<br></br>>>> ie.Quit()
使用 boo 进行开发将显得非常自然
得益于其宽松的语法规则,boo 允许我们以更加轻松快捷的方式完成.NET 平台上的任务。若你正在设计程序的原型,或是创建用户界面元素,那么 boo 将是个绝佳的选择。不仅如此,使用 boo 创建的所有程序或组件均可以无缝地与其他.NET 类库配合使用,无论这些组件是用 C#、VB.NET 还是托管 C++ 编写的。boo 是一种.NET 平台上非常友好的语言,让我们能够不受约束地快速编写代码。还躲什么呢——今天就来试试 boo 吧!
译者简介:陈黎夫(Dflying Chen)是 InfoQ 中文站的志愿者翻译。他毕业于上海交通大学计算机科学专业,曾在微软公司 ASP.NET Ajax 创始团队——Windows Live Hotmail 担任软件开发工程师,使用 ASP.NET Ajax 早期版本参与开发了下一代 Email 系统 Windows Live Mail,以及 Windows Live Calendar 等产品。擅长 Web 相关技术。作为 ASP.NET Ajax 在中国的传道者之一,他在个人博客中写过大量相关技术文章,引起了广泛反响,已经成为国内访问量最大的ASP.NET Ajax 资源之一。著作/ 译作有《 ASP.NET Ajax 程序设计》、《 Atlas 基础教程》和《 CSS 禅意花园》等。加入 InfoQ 中文站志愿者翻译队伍,请邮件至 china-editorial@infoq.com 。
评论