2008年3月14日

REBOL解譯器的執行方式

文 / 蔡學鏞

這篇文章相當重要,只要你懂這篇文章,REBOL剩下的主題,都不會太難。本文章中提到的計算、執行、估算、求值,都是指一樣的概念。

【ANY-WORD】
有的word有綁著(bind)一個值,計算這類的word,就相當於計算它所綁的值。例如:
>> pi
== 3.141592653589793

有的word沒有綁著一個值,計算這類的word,就會得到錯誤訊息。例如:
>> vista
** Script error: vista has no value

將一個word前面加上一個單引號('),就變成lit-wird。如果執行一個lit-word,就會得到它對應的word。例如:
>> 'pi
== pi
>> 'vista
== vista

將一個word後面加上一個冒號(:),就變成set-word。如果計算一個set-word,就會先計算後面的值,然後把這個值綁到word。例如:
>> pi: 3.14
== 3.14
>> pi
== 3.14

你可以把冒號想像成「等於」,所以「pi: 3.14」的意思就是「pi = 3.14」。原本pi被綁到一個很精確的值(3.141592653589793),現在被改成綁到3.14。

執行set-word之後,新的值會再度傳出。如果想一次將多個word設定成相同的值,可以利用這一點,例如:
>> a: b: c: 0

現在a b c都被設定為(綁到)0。

將一個word前面加上一個冒號(:),就變成get-word。如果計算一個get-word,就會得到它所綁的值。例如:
>> :pi
== 3.14

你可能覺得很奇怪,執行get-word和執行word得到的結果不是一樣嗎?
>> pi
== 3.14

雖然這個例子的結果一樣,但是意義不一樣。計算get-word,會取出所綁的值(3.14),直接傳出來;計算word,則多了一個步驟:先取出所綁的值(3.14),然後再計算這個值。只不過3.14計算之後剛好還是3.14。

當word被綁到函數的時候(REBOL有七種函數,請見「各種函數的區別」一文),就不一樣了:
>> now
== 14-Mar-2008/0:06:21+8:00
>> :now
== native!

now被綁到一個native函數,計算「:now」的值,只會得到這個函數,所以你會看到上面的例子寫「== native!」;計算now的值就會造成計算此native函數,因而得到現在的時刻。

將一個word前面加上一個斜線(/),就變成refinement。如果計算一個refinement,就會得到原封不動一樣的refinement。例如:

>> /year
== /year

refinement主要用在函數中,等一下就會看到。

【ANY-PATH】
有一種和word很像的東西,名為path。它由多個word串接起來的,word之間利用「/」當分隔符號。例如:

>> system/version
== 2.99.4.3.1

執行此path,得到的值是「2.99.4.3.1」(整個是一個值)。

path一樣有對應的lit-word、set-word、get-word。例如:
>> 'system/version
== system/version
>> system/user/name
== none
>> system/user/name: "Jerry Tsai"
== "Jerry Tsai"
>> system/user/name
== "Jerry Tsai"
>> :system/user/name
== "Jerry Tsai"

【函數】

前面提到過,如果某word或path被綁到一個函數,那麼計算此word或path就等於計算此函數:
>> now
== 14-Mar-2008/0:06:21+8:00

某些函數word可以加上refinement。例如:你只想取得現在的年份,你可以在now函數的後面,緊接著「/year」refinement:
>> now/year
== 2008

某些函數需要引數(argument),例如「length?」後面可以接著一個字串,傳出字串長度:

>> length? "Jerry"
== 5

大多數的函數,都會先計算出後續的引數,然後才執行函數。以上面的例子來說,"Jerry"計算的結果還是"Jerry",然後傳進length?當引數。下面是另一種寫法:

>> name: "Jerry"
== "Jerry"
>> length? name
== 5

name會先被執行,得到"Jerry"的值,然後把"Jerry"傳進length?當引數。

少部份的函數(例如help、source),不會先估算後續的引數,而是直接將後面的值傳入函數中。例如:

>> help 3.14
3.14 is a decimal
>> help pi
PI is a decimal of value: 3.14

為何第二個訊息會出現PI?因為傳進help函數的引數,是一個word,其值為pi。再看另一個範例:

>> now
== 14-Mar-2008/0:06:21+8:00
>> help 14-Mar-2008/0:06:21+8:00
14-Mar-2008/0:06:21+8:00 is a date
>> help now
USAGE …

「help now」列出了now的使用方式,而不是「… is a date」,這是因為help不會先去估算now的值,而是直接把now這個單字當作值,傳入help中。

那些函數所需要的引數是不會先估算的?這類的引數相當少,而且幾乎都是用在shell中做一些輔助查詢。

常常會一個函數執行到一半,就要跳去執行另一個函數。例如:

>> length? to-local-file what-dir

這個意思是取出目前的目錄(what-dir),將它轉成作業系統的檔案字串(to-local-file),然後計算長度(length?)。感覺好像運算次序變成由右到左,其實還是由左到右。先計算length?,由於length?是一個函數,它需要一個引數,所以從後面找來一個引數,此引數為to-local-file。剛好to-local-file也是一個函數,也需要一個引數,所以從後面取得what-dir的估算結果。有了what-dir的估算結果當引數,to-local-file就可以順利被估算,得到一個結果;有了to-local-file的估算結果當引數,就可以計算length?。

【op】

你可以用下面的方式表達「一加二」:
>> add 1 2
== 3

add是一個函數,需要兩個引數,分別是被加數與加數。最後add將計算的結果傳出來。

你可以用下面的方式表達「一加二減三」:
>> subtract add 1 2 3
== 0

subtract是「減」,後面需要兩個引數,分別是被減數與減數;其中被減數是add,而add又需要兩個引數,分別是1、2。所以「add 1 2」的結果會被當作subtract的第一個引數,最後的3就變成subtract的第二個引數。

你一定很不能接受,「一加二減三」居然要寫成「subtract add 1 2 3」,尤其是subtract居然還出現在add的前面。一個簡單的數學式子用REBOL來表達,竟會如此麻煩。所以在表達數學式子的時候,我們不喜歡用這樣的方式,我們會寫成:

>> 1 + 2 - 3
== 0

其中+與-也都是函數,這種函數稱為op(運算子),出現在兩個引數的中間。也就是說「add 1 2」相當於「1 + 2」

出了出現的位置和一般函數不一樣之外,op還有一點很特殊:一律由左到右的次序計算。例如:

>> 1 - 2 + 3
== 2

先計算「1 - 2」,得到「-1」;再計算「-1 + 3」,得到「2」。

op一律由左到右的次序計算,沒有所謂的「先乘除後加減」,例如:

>> 1 - 2 * 3
== -3

你可以利用括號 ( ) 改變次序,例如:

>> 1 - (2 * 3)
== -5

想知道REBOL有哪些op,在shell輸入:
>> help op!

來看最後一個例子:

>> divide 5 + 4 * 3 2
== 13.5

divide(除法)需要兩個引數,第一個引數是「5 + 4 * 3」,第二個引數是2;「5 + 4 * 3」的值是27,而「divide 27 2」得到13.5。

為何第一個引數是「5 + 4 * 3」,而不是「5」,因為REBOL解譯器注意到,5的後面跟著一個op,而op是一定位於兩個值的中間,所以第一個引數擴大為「5 + 4」;接下來REBOL解譯器又注意到後面又出現一個op,於是再把第一個引數擴大為「5 + 4 * 3」。

5 則留言:

匿名 提到...

关于rebol,请教几个问题。
第一个问题:
>> print set-word? word: "value"
false
>> print set-word? first [word: "value"]
true
上面两组的结果为什么不一样?

第二个问题:
obj: make object! [attr: system/locale]
如何对obj/attr查询,结果是system/locale,而不是
months: [
"January" "February" "March" "April" "May" "June"
"July" "August" "September" "October" "November" "December"
]
days: [
"Monday" "Tuesday" "Wednesday" "Thursday" "Friday" "Saturday" "Sunday"
]

我试过一些函数,好像不可以?是这样吗?

蔡學鏞 / Jerry Tsai 提到...

在下面的例子中
>> print set-word? word: "value"
word被設定為"value"所以是string!,如果你用string?判斷,會得到true,使用set-word?判斷當然得到false

在下面的例子中
>> print set-word? first [word: "value"]
first運算的結果是word:,這當然是set-word!,用set-word?判斷會得到true

蔡學鏞 / Jerry Tsai 提到...

第二個問題,要改寫成:
obj: make object! [attr: 'system/locale]

注意susyem前面有一撇。

匿名 提到...

第二个问题我没有表述清楚,
obj: make object! [attr:system/locale] ,原意是把对象obj的属性attr设置为对象system的属性locale所对应的值,现在来查询定义attr时所写的代码。我想这应该属于解释器如何解释执行代码的范畴,执行的过程中不一定会记录这些信息。

蔡學鏞 / Jerry Tsai 提到...

我其實不是很懂你的意思。大致上,在建立此物件時,設定attr時,已經reduce得到「system/locale的值」,所以obj以及attr已經完全和「system/locale」無關。