歡迎您光臨本站 註冊首頁

魯班語言第四章: 魯班部件組合說明

←手機掃碼閱讀     火星人 @ 2014-03-12 , reply:0
  




http://project.soft114.com/lubankit/index_chinese.html

4. 魯班部件組合入門

Poor boy, pickin' up sticks
Build ya a house out of mortar and bricks
- Po』 Boy, Bob Dylan

到目前為止, 我們看到的魯班代碼都是順序執行的過程代碼. 在第一章里提過, 魯班的一個重要特色是部件組合. 部件組合就是用小的魯班部件連接組合成更大的魯班部件. 連接組合與順序調用不同, 連接組合是將要用的部件列舉出來並定義它們之間數據依賴關係. 組合的魯班部件執行的時候, 裡面的子部件的執行順序是由當時的數據更新的流向決定的.
魯班部件組合與EXCEL很相似. 在EXCEL里每個格子里要不是個常數, 要不是由一個公式. 公式可以從其他格子里的值計算出當前格子的值. 所以每個公式格子都定義了自己和其他格子的關係. 如果你改變一個格子里的值, 所有直接或間接依賴於這個格子的其他格子都會被從新計算賦值. 計算順序由依賴關係決定. 而計算的執行則由數據更新引發.
魯班的組合部件的外部界面與魯班過程部件沒有區別. 所以它們可以混合使用. 你只需要知道部件的界面, 不需要知道它是用組合還是用過程實現的.


6.1 簡單組合舉例
魯班部件組合代碼格式很簡單, 就是一個組合單元列表. 每個組合單元有一個名字和內容定義. 一個組合單元和其他組合單元的數據依賴關係是有它的內容定義決定的. 一個組合單元類似於一個EXCEL的格子. 下面是一個簡單的組合例子.

namespace demo;

struct simplecomp ( output oneoone; )
as composition
{
A1: 100;
A2: A1+1;
output.oneoone: A2;
}

以上魯班組合里有三個組合單元. 單元A1里是一個簡單常數100, A2里是一個表達式」A1+1」. 最後一個單元的名字有點特別, 但它的意思很明確, 就是將單元A2的值放到輸出屬性」oneoone」里.
下面一行魯班代碼調用上面定義的魯班部件」demo::simplecomp」.

result = demo::simplecomp(=).oneoone; // result = 101

以上代碼是標準的魯班部件類型調用, 而且在調用后取出屬性」oneoone」的值賦值給變數」result」. 變數」result」的值是整數101.
為了顯示組合與過程的區別, 我們可以把」demo::simplecomp」部件的代碼改成以下.

namespace demo;

struct simplecomp ( output oneoone; )
as composition
{
output.oneoone: A2;
A2: A1+1;
A1: 100;
}

新的代碼里組合單元的順序是完全顛倒原來的順序. 如果我們再調用這個部件, 結果卻完全不變. 從這兒我們可以看出, 組合部件里單元的順序完全不重要. 重要只的是單元之間的數據依賴關係.

6.2 組合單元詳述
魯班組合部件里的單元有兩類, 一是定型單元, 一是不定型單元. 這一節里我們詳細解說這兩類單元的定義和用途.

6.2.1 不定型單元
到現在我們見過的魯班部件組合里的單元都是不定型單元. 魯班組合不定型單元里可以是常數, 也可以是表達式, 就象我們上面的例子一樣. 不定型單元和EXCEL里的格子可以說很相似. 除了有一點, 魯班的不定型單元里還可以放任意的魯班腳本程序代碼. 這一點是EXCEL做不到的. 下面是一個例子.

namespace demo;

struct compadhoc
(
input in1, in2;
output out;
)
as composition
{
A1: input.in1 + 1;
A2: input.in2 + 1;
A3: { std::println(obj=「run A3」); A3=A1*A2; }
A4: { std::println(obj=「run A4」); A4=A2*A2; }
output.out: A3+A4;
}

以上組合代碼里的單元都是不定型單元. 單元A1和A2里都是簡單表達式, 在運行時魯班會運算表達式然後把結果放在單元里.
單元A3和A4也是不定型單元, 裡面放的是魯班腳本程序. A3和A4里的程序相似, 都是先在屏幕上印出自己運行的信息, 然後給自己運算賦值. 需要注意的是腳本程序單元和表達式單元不同之處. 表達式單元的運算結果直接賦值給單元自己. 腳本程序單元沒有明確單一的運算結果, 給自己單元賦值必須由明確的賦值語句執行. 在A3單元里給自己賦值的語句是」A3=A1*A2」, 這句的意思是把單元A1的值乘以單元A2的值然後賦值給單元A3自己. 在單元A4里給自己賦值的語句是」A4=A2*A2」, 是將單元A2的平方賦值給A4自己. 值得注意的是在不定型單元的定義里, 對其他單元的引用等於建立了當前單元和被引用單元之間的數據依賴關係. 魯班在運行這個部件時就會按這樣的依賴順序和實際的數據更新來決定那個單元會被執行.
上面代碼的最後一個單元是將表達式」A3+A4」連接到輸出屬性」out」.

為了簡單起見, 魯班部件組合的輸出屬性單元, 象以上例子的」output.out」單元, 只能是表達式, 不能是腳本程序.

6.2.2 定型單元
定型單元里放的是已經定義好的魯班部件. 用戶只需要定義單元里的部件的輸入和輸出屬性的連接. 下面是一個例子.

namespace demo;

struct TwoAdder
(
input op1, op2;
output result;
)
as process
{
output.result = input.op1+input.op2;
}

struct FourAdder
(
input op1,op2,op3,op4;
output result;
)
as composition
{
adder1: demo::TwoAdder(op1=input.op1, op2=input.op2);
adder2: demo::TwoAdder(op1=input.op3, op2=input.op4);
adder3: demo::TwoAdder(op1=adder1.result, op2=adder2.result);
output.result: adder3.result;
}

上面的例子是用兩輸入的加法器部件」demo::TwoAdder」來組合成四輸入的加法器」demo::FourAdder」. 我們來看看其中的構造.
程序開始定義了一個簡單的加法器部件」demo::TwoAdder」. 「demo::TwoAdder」的功能是把兩個輸入屬性」op1」, 「op2」的值加起來放到輸出屬性」result」里. 「demo::TwoAdder」是一個過程部件.
關鍵的部分是部件」demo::FourAdder」的構造. 「demo::FourAdder」是一個組合部件. 裡面有三個定型單元」adder1」, 「adder2」, 「adder3」. 三個單元放的都是上面已經定義好的」demo::TwoAdder」部件. 其中」adder1」和」adder2」的輸入連接到」demo::FourAdder」的四個輸入上. 「adder3」的輸入連接到」adder1」和」adder2」的輸出. 最後將」adder3」的輸出連接到」demo::FourAdder」的最終輸出屬性」result」上.
這個例子用三個兩輸入加法器組合成一個四輸入加法器. 用魯班的部件組合構造可以很簡明的定義組合所用部件和它們之間的數據依賴關係.
另外值得注意的是定型單元和不定型單元的區別. 當使用不定型單元時, 其他單元引用其中包含的數據對象值本身. 而使用定型單元時, 其他單元引用其中包含的魯班部件的輸出屬性.

6.3 組合部件內部執行順序
魯班組合部件與過程部件的一大區別是組合部件定義的是內部組合單元之間的數據依賴關係, 而不是執行順序. 組合單元的真正執行順序是由運行時的輸入數據更新決定的. 這一節就講述具體的細節.

還是拿上面用過的例子demo::compadhoc (稍加修改) 來講.

namespace demo;

struct compadhoc
(
input in1, in2;
output out;
)
as composition
{
A1:{ std::println(obj=「run A1」); A1= input.in1 + 1; }
A2: { std::println(obj=「run A2」); A2= input.in2 + 1; }
A3: { std::println(obj=「run A3」); A3=A1*A1; }
A4: { std::println(obj=「run A4」); A4=A2*A2; }
output.out: A3+A4;
}

把上面代碼存入文件 compadhoc.lbn 然後我們調用這個魯班部件 demo::compadhoc . 調用的魯班腳本代碼是:

x = demo::compadhoc(in1=1, in2=2); // 第一行
std::println(obj=x.out); // 第二行
std::println(obj="change input in1"); // 第三行
x.in1=0; // 第四行
std::println(obj=x.out); // 第五行
std::println(obj="change input in2"); // 第六行
y =x(in2=1); // 第七行
std::println(obj=y.out); // 第八行


把以上代碼存入一個叫 start.lbn 的文件. 然後啟動執行:

Mycomputer > luban compadhoc.lbn start.lbn
run A1
run A2
run A3
run A4
13
change input in1
run A1
run A3
10
change input in2
run A2
run A4
5

以下逐行解釋以上程序的運行輸出.
* start.lbn的第一行是調用部件demo::compadhoc, 調用時把輸入in1設置成1, in2設置成2, 最後把調用是產生的新部件對象賦值給變數x.. 調用時魯班運行部件demo::compadhoc, 運行時列印出:
run A1
run A2
run A3
run A4

在demo::compadhoc部件內部, 數據流依賴關係是這樣定義的:

input.in1 --> A1 --> A3 ?----
| -->output.out
input.in2 --> A2 --> A4 ----

從上面的數據流看, A1一定在A3之前, A2一定在A4之前. 上面的程序輸出」run A1 run A2run A3 run A4」是滿足數據流的要求的.
另外有一點需要注意的是, 對於沒有數據依賴關係的單元,它們的執行順序關係是沒有定義的. 比如上面例子里的A1和A2, A3和A4之間都沒有依賴關係. 所以A1和A2哪個先執行, A3和A4哪個先執行都是沒有定義的. 換句話說, 如果運行結果是」run A2 run A1 run A4 run A3」, 這也是符合魯班語言定義的.
這個魯班部件的功能是計算 (in1+1)x(in1+1) + (in2+1)x(in2+1). 所以之後列印的結果是 13 (in1=1, in2=2)

* 第四行是改變魯班部件x的輸入in1的值成0. 這裡請讀者注意, 魯班組合部件和過程部件的最大區別就在這裡. 記得以前講述過, 如果部件的輸入改變會引發部件的運行. 魯班組合部件也是這樣. 但是和魯班過程部件不一樣的是, 魯班過程部件運行時必須運行整個過程, 而魯班組合部件只運行那些和被改變的輸入有數據依賴關係的單元. 換句話說, 魯班組合部件只執行從被改變的輸入起的數據流下游的組合單元.
在這個例子里, 輸入in1的數據流下游的組合單元有A1和A3, 所以執行時列印出」run A1 run A3」. 執行后輸出結果變成10, 所以在第五行代碼列印輸出時印出10.

* 第七行是動態調用魯班部件對象x並把輸入in2的值設置成1. 和上面講述的相似, 動態調用時魯班組合部件只執行從被改變的輸入起的數據流下游的組合單元.
在這裡, 輸入in2的數據流下游的組合單元有A2和A4, 所以執行時列印出」run A2 run A4」. 執行后輸出結果變成5, 所以在最後一行代碼列印輸出時印出5.

從以上例子可以得出以下結論:
1. 魯班組合部件的內部單元執行順序由數據流依賴關係決定. 彼此之間沒有數據依賴關係的單元, 則沒有明確定義的順序關係.
2. 改變組合部件的輸入屬性或者動態調用組合部件對象, 只引發被改變的輸入起的數據流下游的組合單元執行.

魯班組合部件的根據輸入屬性改變來決定部分執行組合單元的特色, 可能對於很多應用環境可能很方便.


6.4 數據流定義規則
在編寫魯班組合部件時, 用戶可以將表達式, 魯班腳本程序和已定義的魯班部件放到組合單元里. 魯班語言會自己找出單元之間的數據流依賴關係來決定運行的單元和它們之間的順序. 下面講述在定義組合單元時必須遵守的規則.

6.4.1能改變自己不能改變外界
對於魯班組合部件里的單元來說, 外面的其他單元都是只能讀不能寫的. 一個單元可以改變的是自己的內容. 如果一個單元試圖去改變其他單元的內容, 魯班會報錯. 直接給單元賦值,調用單元的成語函數和改變單元屬性都是改變單元內容的操作.不能改變外界只能改變自己的規則是為了保證魯班組合部件里的數 據流是簡明單向的.在一個單元里改變其他單元的內容會使數據流變得複雜和難以理解.下面是一個例子.

namespace demo;

struct WrongComp()
as composition
{
A1: 1;
A2: { A1=0; A2=A1+1; }
}

把以上程序存入一個文件wrongcomp.lbn里,然後用以下命令啟動執行.

Mycomputer > luban wrongcomp.lbn ?s 「demo::WrongComp(=);」

Error when executing Luban code from stdin:
Failed to resolve external symbols for struct evaluation: Can not set cell content other than self: A1
demo::WrongComp has error and can not be used
Can not resolve external symbol demo::WrongComp

上面魯班程序運行時顯示出錯誤信息說,不能給除了自己以外的其他單元賦值

6.4.2 數據流不能循環
魯班組合部件不允許在同步單元之間存在循環數據依賴關係.同步單元和非同步單元的區別以後還會講述.在這裡大家只要知道,到目前為止講述的所有單元都是同步單元就好了.下面是一個循環依賴關係的例子.

namespace demo;

struct CyclicComp()
as composition
{
A1: A2+1;
A2: A1-1;
}

上面程序里的單元A1和A2相互引用, 成為循環依賴關係. 這個魯班組合部件是不合法的. 把以上程序存入一個文件cycliccomp.lbn里,然後用以下命令啟動執行.

Mycomputer > luban cycliccomp.lbn ?s 「demo::CyclicComp(=);」
ERROR: Struct evaluation exception: Failed to initialize composition struct for evaluation: Invalid composition, cyclic path involving: A2 A1

魯班報錯說有循環依賴關係, 而且魯班還列舉了循環圈裡的單元. 這個例子里單元A1和A2互相引用, 所以上面信息里說A2和A1組成循環.

6.5 過程與組合的選擇
現在大家知道定義一個魯班部件可以有兩種方式, 一是傳統的過程, 二是用組合. 組合方式很多人其實在用EXCEL Spreadsheet時就是在用組合方式編程序. 魯班的組合部件是又向前進了一大步. 相比EXCEL Spreadsheet, 組合的魯班部件有明確的界面可以重用和共享.
一般來說, 如果一個部件內部有複雜的控制結構, 比如循環, 比較, 跳轉等, 這樣的部件應該用傳統的過程代碼來編寫. 一般來說, 這樣的情況在相對較小的部件里比較常見.
如果一個部件內部可以看成一些相對獨立的單元, 而在單元之間有明確的數據流依賴關係. 這樣的部件就很適合用組合來編寫. 一般來說, 在高層的部件里這樣的組合比較常見.
還有可能是純粹是用戶個人選擇. 比如說, 一個用慣了EXCEL Spreadsheet的人會發現組合方式編程很自然.

6.6 部件界面繼承和非同步單元
魯班組合里的還有兩個很有用的特色會在後面相關章節講述. 其中和部件界面繼有關的構造會在下一章講述. 和多線程有關的非同步單元會在講述非同步部件的第七章講述.



[火星人 ] 魯班語言第四章: 魯班部件組合說明已經有284次圍觀

http://coctec.com/docs/program/show-post-71890.html