一篇文章看完就让你能迅速读懂tcl程序==被误解的Tcl (Tcl the Misunders...

被误解的Tcl (Tcl the Misunderstood) – 为何Tcl是一门强大的语言,而不是玩具

TCL, 翻译html

by liding ios

Notice程序员

原文见http://antirez.com/articoli/tclmisunderstood.html,版权归原文做者全部;译文Copyright © 2012 Li Ding。转载请注明转自dingmaotu.com正则表达式

最近有一篇连接自reddit的题为Tour de Babel的文章,若是你读的话就会发现这篇文章说(除了一堆其余的胡言乱语):靠,Python在能想象到的各方面都比Tcl好得多了,但人们还用Tcl来看成嵌入式解释器。shell

好吧,整篇文章是有点……不是那么回事。可是不幸的是,虽然不少错误观念很快被知情的读者发觉了,可是这个反对Tcl的观念却被认为是理所固然的。我但愿这篇文章能说服人们,Tcl尚未那么不堪。编程

开场白

在个人编程生涯里,我使用了不少语言去写不一样的应用:用C语言写了不少免费/付费的程序,用Scheme写了一个Web CMS(内容管理系统),用Tcl写了几个网络/Web应用,用Python写了一个商店管理程序,等等。我也玩过很多其余的编程语言,例如Smalltalk,Self,FORTH,Ruby,Joy……然而,我从不怀疑,没有哪门语言像Tcl同样被误解得如此之深缓存

Tcl不是完美无瑕,但大多数的不足并非在语言设计自己,而是Tcl“之父”(John Ousterhout)几年前去世了,连同他那种能够作出强势决定的专注的领导力。只要作出正确的改变,克服Tcl的大多数不足并保留其强大功能是能够的。若是你不相信Tcl异常强大,请先花点时间来阅读这篇文章。可能你读完以后仍是不喜欢它,但但愿你尊敬它,同时你将有足够强大的论据来反对这种“Tcl是玩具语言”的误解。这种误解如此小气,比“Lisp括号太多”更甚。安全

在咱们开始以前,我先花点时间解释一下Tcl的工做原理。Tcl像世界上其余优秀的语言同样,拥有一些概念,这些概念组合起来,可以实现编程自由和充分的表达力。服务器

在这个简短的介绍以后,你会了解在Tcl中,怎样使用普经过程(procedure)实现像Lisp同样的宏(macro)(比Ruby的Block强大得多),怎样重定义语言自己的几乎全部方面,怎样在编程时忽略类型。Tcl社区开发了数个OOP系统,大规模的语言重定义,宏系统,和不少其余有趣的东西,仅仅使用Tcl自己。若是你喜欢可编程的编程语言,我打赌,你确定至少饶有兴趣地看一眼Tcl。babel

五分钟学会Tcl

概念1:程序由命令(Command)组成

Tcl语言的第一个观念是:命令。程序就是一系列命令。例如把变量a设成5,并输出其值:

set a 5
puts $a

命令是空格分隔的单词。命令以换行或“;”结束。Tcl中一切皆是命令——正如你所见,没有赋值运算符。设置变量须要使用命令“set”,把命令的第一个参数设为第二个参数的值。

几乎全部的Tcl命令都返回一个值。例如“set”返回所赋值的值,若是set只有一个参数(即变量名),就变量的当前值。

概念2:命令替换(Command substitution)

第二个观念是命令替换。一个命令中,有些参数出如今“[]”中。若是是这样,那个参数的值就是中括号中命令的返回值。例如:

set a 5
puts [set a]

第二个命令的第一个参数[set a],将会被替换为“set a”的返回值(也就是5)。在替换后,命令将由

puts [set a]

变成

puts $a

这时,命令才会被执行。

概念3:变量替换(Variable substitution)

老是使用set命令来替换变量太麻烦了,因此,即便不是绝对必要,变量替换在Tcl早期发展中被添加进来。若是一个变量名字以前有$号,就会被它的值所替换。例如能够不用写

puts [set a]


而是写成

puts $a

 

概念4:组合(Grouping)

若是命令是由空格分隔的单词,怎样处理包含空格的变量呢?例如

puts Hello World

是不正确的,由于“Hello”和“World”是两个不用的参数。这个问题由组合来解决。在“”"”中的文本被是单个参数,因此正确的写法是:

puts "Hello World"

命令和变量替换在这种组合中仍然有效,例如我能够这样写:

set a 5
set b foobar
puts "Hello $a World [string length $b]"

结果是“Hello 5 World 6”。另外,转义字符(例如“\t”,“\n”)也是有效的。可是有另一种组合,每种特殊字符都被原样看待,没有替代过程。Tcl把任何在“{}”之间的东西当作是单一的参数,没有替换。因此:

set a 5
puts {Hello $a World}

将会输出“Hello $a World”。

概念1第二次:一切都是命令

概念1是:程序由一系列命令组成。实际上,这比你想象的还要正确。例如:

set a 5
if $a {
    puts Hello!
}

“if”是个命令,有两个参数。第一个是变量“a”所替换的值,第二个是字符串 “{… puts Hello! …}”。“if”命令使用一种特殊的Eval命令(后面将会讲到),运行第二个参数,而后返回结果。固然,你也能够写本身的“if”命令版本,或者其余任何控制结构。你甚至能够重定义“if”,加入一些新功能!

概念5:一切都是字符串——没有类型

如下程序能正常运行,而且结果如你所想:

set a pu
set b ts
$a$b "Hello World"

是的,Tcl中一切发生在运行时,而且是动态的:Tcl是终极迟绑定(late binding)语言,没有类型。命令名不是特殊类型,只是一个字符串。数字也是字符串,正如Tcl代码(还记得咱们传给“if”命令第二个参数一个字符串吗?)。Tcl中,字符串表示什么由处理它的命令所决定。字符串“5”将会在命令“string length 5”中被当作一个字符,而在“if $a”中当作一个布尔值。固然命令会检查它的参数值有正确的形式。若是我要把“foo”和“bar”加起来,Tcl会产生异常,由于没法讲“foo”和“bar”解析成数字。Tcl中这种检查很是严格,因此你不会遇到PHP那种荒谬的隐式类型转换。字符串能够被解释成命令想要的值时,类型转换才会发生。

那么,Tcl如此动态,你猜怎么着?它或多或少和当前的Ruby实现同样快速。Tcl实现中有个技巧:对象(不是OOP中的对象,而是表明Tcl值的C结构)会缓存最后使用的某个字符串的本地值(译者注:例如“56”这个字符串表明一个int,会被缓存起来,下次就不用再分析一遍了)。若是一个Tcl值一直被看成数字来用,只要下一个命令继续把他看成数字,字符串的表示根本不会修改。实际的实现比上述复杂,但整体结果是:程序员不用管类型,程序仍然和其余显式类型的语言同样快。

概念6:Tcl列表(list)

Tcl使用的一种更有趣的类型(更准确点……字符串格式)是列表。列表是Tcl程序的中心数据结构:一个Tcl列表永远是一个有效的Tcl命令!(最后它们都是字符串)。最简单的列表就是命令:空格分隔的单词。例如字符串“a b foo bar”是一个有四个元素的列表。有各类操做列表的命令:取一个list中的元素,添加元素,等等。固然,列表可能有含空格的元素,因此为了建立格式良好的列表,就使用list命令。例如:

set l [list a b foo "hello world"]
puts [llength $l]

llength返回列表的长度,因此上述程序将输出4。lindex返回在某个位置的元素,因此“lindex $l 2”将返回“foo”。和Lisp同样,大多数Tcl程序员使用列表来模拟全部可能的概念。

概念7:Tcl数学运算

我打赌大多数Lisp黑客已经注意到Tcl是一个前缀(prefix)表达式语言,因此你可能认为像Lisp同样,Tcl数学运算就像使用命令,例如“puts [+ 1 2]”。然而,偏偏相反,为了使Tcl更加友好,有个命令接受中缀(infix)数学表达式,并计算其值。这个命令是“expr”,Tcl数学运算像这样:

set a 10
set b 20
puts [expr $a+$b]

“if”和“while”等命令内部使用“expr”来计算表达式,例如:

while {$a < $b} {
    puts Hello
}

其中,“while”命令接受两个参数——第一个字符串求值,看看在每次循环时是否为真,第二个每次被分析执行。我认为数学命令不是内置是一个设计上的错误。在作复杂运算时,“expr”很酷,可是仅仅把两个数相加,仍是“[+ $a $b]”更加方便一些。值得注意的是,这点已经被正式提出,做为对语言的修改。

概念8:过程(Procedures)

天然,没有什么能阻止Tcl程序员写一个过程(即用户定义命令),来把数学操做符看成命令。就像这样:

proc + {a b} {
    expr {$a+$b}
}

“proc”命令用来建立一个过程:第一个参数是过程名,第二个是参数列表,最后一个是过程的主体。注意第二个参数,参数列表,是一个Tcl列表。正如你所见,最后一个命令的返回值是过程的返回值(除非显式使用return)。可是等一下……Tcl中一切都是命令,是吧?因此咱们能够用更简单的方式建立“+、-、*、……”的过程:

set operators [list + - * /]
foreach o $operators {
    proc $o {a b} [list expr "\$a $o \$b"]
}

定义这些以后,咱们就能够用“[+ 1 2] [/ 10 2]”等表达式了。固然,把这些过程建立成相似Scheme过程同样的变长参数更好一些。Tcl过程可使用内置命令的名字,因此你能够重定义Tcl自己。例如为了写了一个Tcl宏处理系统我重定义了“proc”。重定义“proc”一般对编写分析器(profiler)是颇有用的(Tcl分析器是使用Tcl开发的)。在重定义内置命令以前,若是你把它重命名,那么在定义以后仍是能够调用原来的命令的。

概念9:Eval和Uplevel

若是你读这篇文章,说明你已经知道Eval是什么了。命令“eval {puts hello}”固然会执行传递给eval的参数,在其余语言中也很常见。而Tcl还有另外一个命令uplevel,能够在调用过程的上下文中执行语句(译者注:即在当前上下文的上一层上下文),或者说,在调用者的调用者的上下文中。这就是说,Lisp中的宏,在Tcl中就是简单的过程。例如:Tcl中没有内置的命令“repeat”:

repeat 5 {
    puts "Hello five times"
}

可是写一个实现很是容易:

proc repeat {n body} {
    set res ""
    while {$n} {
        incr n -1
        set res [uplevel $body]
    }
    return $res
}

注意,咱们用心保存最后一次执行的结果,因此咱们的“repeat”像其余命令同样,返回最后执行的值。一个例子:

set a 10
repeat 5 {incr a} ;# Repeat will return 15

正如你猜想的,“incr”命令用来把整数变量加1(若是你忽略了第二个参数的话)。“incr a”在调用过程的上下文中执行(即前一个栈帧)。

祝贺,你已经知道了90%以上的Tcl概念!

为何Tcl如此强大?

我不会想你展现每个Tcl特性,可是我将给你一个直观感觉,看看Tcl怎样很是漂亮地解决高级编程任务的。我想强调我认为Tcl确实有一些错误,可是大多不在语言自己的主要概念以内。我认为,在Web编程,网络编程,GUI开发,DSL,脚本语言等方面,一个继承自Tcl的编程语言有和Ruby、Lisp和Python竞争的空间。

易扩展的简洁语法

Tcl语法如此简单,你能够用数行Tcl代码写一个Tcl分析器。正如我所说,我用Tcl语言写了一个Tcl宏处理系统,这个系统可以进行足够复杂的源码级的变换实现尾部调用优化(tail call optimization)。同时,Tcl语法可以变化成Algol同样,这取决于你的编程风格。

没有类型,但有严格的格式检查

没有类型,你不须要进行转换,可是,你不大可能引进bug,由于对字符串的格式检查很是严格。更好的是,你不须要序列化(Serialization)。你有一个巨大复杂的Tcl列表,想把它经过TCP套接字发送出去?这样就好了:“puts $socket $mylist”。另外一头读取:“set mylist [read $socket]”。这样就好了。

强大的、事件驱动的I/O模型

Tcl有内置的事件驱动编程,和I/O库集成在一块儿。只使用核心语言所提供的功能来写复杂的网络程序如此简单,甚至是有趣。例如:如下程序是一个并行TCP服务器(内部基于select(2)),它把当前时间送给每一个客户端。

socket -server handler 9999
proc handler {fd clientaddr clientport} {
    set t [clock format [clock seconds]]
    puts $fd "Hello $clientaddr:$clientport, current date is $t"
    close $fd
}
vwait forever

非阻塞I/O和事件处理得如此之好,你甚至能够向一个没有输出缓存的套接字写入,Tcl自动在用户态缓存,当再次有输出缓存时,在后台发送出去。

Python用户看到某个理念时就会知道它是一个好主意——Python的“Twisted”框架使用了相同的select驱动的IO概念,而这个在Tcl自己中存在好多年了。(译者注:貌似Node.js的核心理念也是这个吧,看来Tcl超前了)

多种编程范式

使用Tcl你能够混合编写面向对象代码,函数式代码,和命令式代码,或多或少像Common Lisp那样。过去不少OOP系统和函数式编程原语都被实现出来。Tcl有全部的范式,从基于原型的OOP(译者注:Javascript那样的)到相似Smalltalk的那种,不少是以Tcl自己实现的(或者一开始做为概念论证原型)。并且,由于Tcl中代码是一级类型,很容易写出函数式语言原语,并和原语言结合很好。“lmap”的一个例子:

lmap i {1 2 3 4 5} {
    expr $i*$i
}

这将会返回平方列表“1 4 9 16 25”。你能够写相似“map”的函数,基于一个lambda版本(也是用Tcl实现的),可是Tcl已经有拥有比Lisp更天然的函数式编程特性(Lisp方式可能对它自己很好,可是对其余语言来讲就不必定了)。注意当你向一个过于死板的语言中加入函数式编程的时候会发生什么:Python以及它函数式原语的无尽争论。

中心数据结构:列表

若是你是个Lisp程序员,你知道若是在程序中有列表随处可用是多么美妙,尤为是列表的直接形式在大多数状况下如同“foo bar 3 4 5 6”同样简单。

经过uplevel的可编程编程语言

经过Tcl的eval、uplevel、upvar,以及很是强大的内省能力,你能够重定义语言并发明解决问题的新方式。例如如下有趣的命令,若是把它放在函数的第一行调用,将自动使那个函数成为一个memoizing函数(译者注:一种把函数返回值缓存起来的方法,读者能够搜所Javascript的实现):

proc memoize {} {
    set cmd [info level -1]
    if {[info level] > 2 && [lindex [info level -2] 0] eq "memoize"} return
    if {![info exists ::Memo($cmd)]} {set ::Memo($cmd) [eval $cmd]}
    return -code return $::Memo($cmd)
}

而后当你写一个过程的时候,这样就好了:

proc myMemoizingProcedure { ... } {
    memoize
    ... the rest of the code ...
}

i18n自动支持

Tcl多是拥有最好的国际化支持的语言了。每一个字符串内部使用utf-8表示,全部的字符串是Unicode安全的,包括正则表达式引擎。基本上,在Tcl程序里,编码不是个问题,他们自动工做。

大规模语言修改=DSL

若是你定义了一个过程叫作“unknown”,这个过程将会在Tcl处理命令出错时,将会把命令的参数传递给它,并调用之。你能够在这个过程当中作任何你想作的事,返回一个值,或者引起错误。若是你只是返回一个值,那么被调用的命令就像没出错同样,并用“unknown”的返回值做为它的返回值。把这一点加在uplevel和upvar之上,这门语言几乎没有语法规则了。你所获得的是一个使人印象深入的领域特定语言的开发环境。Tcl基本没有语法,就像Lisp和FORTH,可是“没有语法”的方式不一样。Tcl默认状况下就像一个配置文件:

disable ssl
validUsers jim barbara carmelo
hostname foobar {
    allow from 2:00 to 8:00
}

以上是合法的Tcl程序,只要你定义了所用的命令(disable、validUsers和hostname)。

更多

不幸的是,没有太多空间来展现不少有趣的特性:大多数Tcl命令只作一件事,而且名称容易记忆。字符串操做,内省和其余特性经过拥有子命令的命令实现。例如“string length”,“string range”等等。每一个须要索引的地方都支持一种“end-数字”的记号,所以取一个列表除了第一和最后一个的全部元素,你这样写就好了:

lrange $mylist 1 end-1

而且,对常见代码都有不少很好的设计和优化。另外,Tcl源代码是你所能找到的最好的C程序之一,解释器的质量使人吃惊:无论哪方面说都是商业级别的。另外一个关于实现的有趣事实是,它在不一样的环境下有彻底相同的工做表现,从Windows到Unix,再到Mac OS X。在不一样平台上没有质量差异(是的,包括Tk,Tcl主要的GUI库)。

结论

我并无声称每一个人都该喜欢Tcl。我说的是Tcl是个强大的语言而不是一个玩具,并且可能创造一个新的相似Tcl的语言,没有Tcl的肯定,并且拥有它全部的强大能力。我本身试过,结果是Jim interpreter:代码就在那里,能够正常工做,能运行大多数Tcl程序,可是我没有时间去作自由语言开发,因此这个项目或多或少已经废弃了。另外一个企图开发一个类Tcl语言的项目是Hecl,正在进行中。这个语言做为Java语言的脚本语言,它的做者(David Welton)意识到Tcl核心实现很小,基于命令的设计容易做为两个语言之间的联系(这在现代动态语言中少见,但这两个特性也试用与Scheme)。我将很是高兴,若是你读了这篇文章以后,再也不认为Tcl是个玩具。谢谢。Salvatore。

相关文章
相关标签/搜索