• <ins id="pjuwb"></ins>
    <blockquote id="pjuwb"><pre id="pjuwb"></pre></blockquote>
    <noscript id="pjuwb"></noscript>
          <sup id="pjuwb"><pre id="pjuwb"></pre></sup>
            <dd id="pjuwb"></dd>
            <abbr id="pjuwb"></abbr>
            隨筆-341  評(píng)論-2670  文章-0  trackbacks-0

            面向?qū)ο筮@個(gè)抽象的特例總是有說不完的話題,更糟糕的是很多語言都錯(cuò)誤地實(shí)現(xiàn)了面向?qū)ο蟆猚lass居然可以當(dāng)一個(gè)變量類型什么的這只是讓人們寫代碼寫的更糟糕而已。當(dāng)然這個(gè)話題第三篇文章已經(jīng)說過了,現(xiàn)在來談?wù)勅藗兿矚g拿來裝逼的另一個(gè)話題——消息發(fā)送。

            按照慣例先來點(diǎn)題外話。說到消息發(fā)送,有些人喜歡跳出來說,objective-c的消息做得多優(yōu)雅啊,代碼都可以寫成一句話[golang screw:you you:suck]之類的。其實(shí)這個(gè)還做得不夠徹底。在幾年前易語言曾經(jīng)火了一陣,但是為什么大家這么討厭他呢?其實(shí)顯然不是因?yàn)槊總€(gè)token都是漢字,而是因?yàn)樗龅囊稽c(diǎn)都不像中文,誰會(huì)說話的時(shí)候帶那么多符號(hào)呀。其實(shí)objective-c也一樣,沒人會(huì)因?yàn)橄朐谝痪溆⒄Z里面用冒號(hào)來分割短語的。

            當(dāng)我還在讀大三的時(shí)候,我由于受到了Apple Script(也是蘋果做的)的啟發(fā),試圖發(fā)明一門語言,讓他可以盡量寫起來像自然語言——當(dāng)然他仍然是嚴(yán)格的編程語言。但是這門語言因?yàn)槠淦嫣氐恼Z法結(jié)構(gòu),我只好自己想出了一個(gè)兩遍parse地方法。第一遍parse出所有函數(shù)頭,讓后用這些函數(shù)頭臨時(shí)組成一個(gè)parser,用它來parse語句的部分。后面整個(gè)也實(shí)現(xiàn)出來了,然后我就去做了一下調(diào)查,發(fā)現(xiàn)大家不喜歡,原因是要輸入的東西太多了。不過我這里還是貼一下當(dāng)初是怎么設(shè)計(jì)的:

            phrase print(content) is
                external function "writeln"
            end phrase
            
            phrase first (count) items of fibonacci sequence is
                if count equals to 1 then
                    result is [1]
                else if count equals to 2 then
                    result is [1,1]
                else
                    let list be [1,1]
                    repeat with i from 3 to count
                        let list be list joins with item length of list - 1 of list + item length of list - 2 of list
                    end
                    result is list
                end
            end phrase
            
            phrase (number) is odd is
                result is number mod 2 is 0
            end phrase alias odd number
            
            phrase append (item) after (list) is
                let 0 element of list from length of list be [item]
            end phrase
            
            phrase ((item) is validated) in (list) is
                let filtered list be []
                repeat with item in list
                    append item after filtered list if item is validated
                end
                result is filtered list
            end phrase
            
            phrase main is
                print odd number in first 10 items of fibonacci sequence
            end phrase

            倒數(shù)第二個(gè)函數(shù)聲明甚至連函數(shù)指針的聲明也如此的優(yōu)雅(我自己認(rèn)為的),整個(gè)程序組織起來,我們要輸出斐波那契數(shù)列里面前10個(gè)數(shù)字中間的奇數(shù),于是就寫成了

            print odd number in first 10 items of fibonacci sequence

            看起來比objective-c要漂亮把。其實(shí)如果想把所有的東西換成中文,算法也不需要變化。現(xiàn)在用空格來分割一個(gè)一個(gè)的詞,中文直接用字符就好了,剩下的都一樣。要parse這個(gè)程序根本沒辦法用流行的那些方法來parse。當(dāng)然我知道大家也不會(huì)關(guān)心這些特別復(fù)雜的問題,于是題外話就到這里結(jié)束了,這個(gè)語言的實(shí)現(xiàn)的代碼你們大概也永遠(yuǎn)都不會(huì)看到的,啊哈哈哈哈。

            為什么要提這件事情呢?我主要是想告訴大家,就算你在用面向?qū)ο笳Z言,想在程序里面給一個(gè)對象發(fā)送一條消息,這個(gè)對象并不是非得寫在最前面的。為什么呢?有的時(shí)候?qū)ο蟛恢挂粋€(gè)——這個(gè)東西叫multiple dispatching,著名的問題就是如何給一堆面向?qū)ο蟮膸缀误w類做他們的求交函數(shù)——用面向?qū)ο蟮膽T用做法做起來會(huì)特別的難受。不過現(xiàn)在我們先來看一下普通的消息發(fā)送是什么樣子的。

            對于一個(gè)我們知道他是什么類型的對象來說,發(fā)送一個(gè)消息就跟直接調(diào)用一個(gè)函數(shù)一樣,因?yàn)槟悴恍枰esolve一下這個(gè)函數(shù)到底是誰。譬如說下面的代碼:

            class Language
            {
            public:
                void YouSuck(){ ... }
            };
            
            Language golang;
            golang.YouSuck();

            最終翻譯出來的結(jié)果會(huì)近似

            struct Language
            {
            };
            
            void Language_YouSuck(Language* const this)
            {
                ...
            }
            
            Language golang;
            Language_YouSuck(&golang);

            很多人其實(shí)并不能在學(xué)習(xí)面向?qū)ο笳Z言的時(shí)候就直接意識(shí)到這一點(diǎn)。其實(shí)我也是在高中的時(shí)候玩delphi突然就在網(wǎng)上看見了這么一篇文章,然后我才明白的。看起來這個(gè)過渡并不是特別的自然是不是。

            當(dāng)你要寫一個(gè)獨(dú)立的class,不繼承自任何東西的時(shí)候,這個(gè)class的作用只有兩個(gè)。第一個(gè)是封裝,這個(gè)第三篇文章已經(jīng)提到過了。第二個(gè)作用就是給里面的那些函數(shù)做一個(gè)匿名的namespace。這是什么意思呢?就像上面的代碼一樣,你寫golang.YouSuck(),編譯器會(huì)知道golang是一個(gè)Language,然后去調(diào)用Language::YouSuck()。如果你調(diào)用lisp.YouSuck()的時(shí)候,說不定lisp是另一個(gè)叫做BetterThanGolangLanguage的類型,然后他就去里面找了YouSuck。這里并不會(huì)因?yàn)閮蓚€(gè)YouSuck的名字一樣,編譯器就把它搞混了。這個(gè)東西這跟重載也差不多,我就曾經(jīng)在Microsoft Research里面看見過一個(gè)人做了一個(gè)語言(主要是用來驗(yàn)證語言本身的正確性的),其中a.b(c, d)是b(a, c, d)的語法糖,這個(gè)“.”毫無特別之處。

            有一天,情況變了。專門開發(fā)蹩腳編譯器的AMD公司看見golang很符合他們的口味,于是也寫了一個(gè)golang的實(shí)現(xiàn)。那這個(gè)事情應(yīng)該怎么建模呢?因?yàn)間olang本身是一套標(biāo)準(zhǔn),你可也可以稱呼他為協(xié)議,然后下面有若干個(gè)實(shí)現(xiàn)。所以Language本身作為一個(gè)category也只好跟著golang變成interface了。為了程序簡單我們只看其中的一個(gè)小片段:

            class IGolang
            {
            public:
                virtual void YouSuck()=0;
            };
            
            class GoogleGolang : public IGolang
            {
            public:
                void YouSuck()override{ /*1*/ }
            };
            
            class AmdGolang : public IGolang
            {
            public:
                void YouSuck()override{ /*2*/ }
            };
            
            IGolang* golang = new GoogleGolang;
            golang->YouSuck();

            我很喜歡VC++的專有關(guān)鍵字override,他可以在我想override但是不小心寫錯(cuò)了一點(diǎn)的時(shí)候提示我,避免了我大量的錯(cuò)誤的發(fā)生。當(dāng)然這個(gè)東西別的編譯器不支持,所以我在我的代碼的靠前的地方寫了一個(gè)宏,發(fā)現(xiàn)不是VC++再編譯,我就把override給#define成空的。反正我的程序里面不會(huì)用關(guān)鍵字來當(dāng)變量名的。

            看著這個(gè)程序,已經(jīng)不能單純的用GoogleGolang_YouSuck(golang)來代替這個(gè)消息發(fā)送了,因?yàn)轭愋褪荌Golang的話說不定下面是一個(gè)AmdGolang。所以在這里我們就要引入虛函數(shù)表了。一旦引入了虛函數(shù)表,代碼就會(huì)瞬間變得復(fù)雜起來。我見過很多人問,虛函數(shù)表那么大,要是每一個(gè)類的實(shí)例都帶一個(gè)表的話豈不是很浪費(fèi)內(nèi)存?這種人就應(yīng)該先去看《Inside the C++ Object Model》,然后再反省一下自己的問題有多么的——呃——先看帶有虛函數(shù)表的程序長什么樣子好了:

            struct vtable_IGolang
            {
                void (*YouSuck)(IGolang* const this);
            };
            
            struct IGolang
            {
                vtable_IGolang* vtable;
            };
            
            //---------------------------------------------------
            
            vtable_IGolang vtable_GoogleGolang;
            vtable_GoogleGolang.YouSuck = &vtable_GoogleGolang_YouSuck;
            
            struct GoogleGolang
            {
                IGolang parent;
            };
            
            void vtable_GoogleGolang_YouSuck(IGolang* const this)
            {
                int offset=(int)(&((GoogleGolang*)0)->parent);
                GoogleGolang_YouSuck((GoogleGolang*)((char*)this-offset));
            }
            
            void GoogleGolang_YouSuck(GoogleGolang* const this)
            {
                /*1*/
            }
            
            void GoogleGolang_ctor(GoogleGolang* const this)
            {
                this->parent->vtable = &vtable_GoogleGolang;
            }
            
            //---------------------------------------------------
            // AmdGolang略,長得都一樣
            //---------------------------------------------------
            
            GoogleGolang* tmp = (GoogleGolang*)malloc(sizeof(GoogleGolang));
            GoogleGolang_ctor(tmp);
            IGolang* golang = &tmp->parent;
            golang->vtable->YouSuck(golang);

            基本上已經(jīng)面目全非了。當(dāng)然實(shí)際上C++生成的代碼比這個(gè)要復(fù)雜得多。我這里只是不想把那些細(xì)節(jié)牽引進(jìn)來,針對我們的那個(gè)例子寫了個(gè)可能的實(shí)現(xiàn)。面向?qū)ο蟮恼Z法糖多么的重要啊,盡管你也可以在需要的時(shí)候用C語言把這些東西寫出來(就跟那個(gè)愚蠢的某著名linux GUI框架一樣),但是可讀性已經(jīng)完全喪失了吧。明明那么幾行就可以表達(dá)出來的東西,我們?yōu)榱诉_(dá)到同樣的性能,用C寫要把代碼寫成屎。東西一多,名字用完了,都只好對著代碼發(fā)呆了,決定把C扔了,完全用C++來寫。萬一哪天用到了virtual繼承——在某些情況下其實(shí)是相當(dāng)好用的,譬如說第三篇文章講的,在C++里面用interface,而且也很常見——那用C就只能呵呵呵了,寫出來的代碼再也沒法讀了,沒法再把OOP實(shí)踐下去了。

            好了,消息發(fā)送的簡單的實(shí)現(xiàn)大概也就講到這里了。只要不是C++,其他語言譬如說只有單根繼承的Delphi,實(shí)現(xiàn)OOP大概也就是上面這個(gè)樣子。于是我們圍繞著消息發(fā)送的語法糖玩了很久,終于遇到了兩大終極問題。這兩個(gè)問題說白了都是開放和封閉的矛盾。我們用基類和一大堆子類的結(jié)構(gòu)來寫程序的時(shí)候,需要把邏輯都封裝在虛函數(shù)里面,不然的話你就得cast了,cast是將程序最終導(dǎo)向失控的根源之一。這個(gè)時(shí)候我們對類型擴(kuò)展是開放的,而對邏輯擴(kuò)展是封閉的。這是什么意思呢?讓我們來看下面這個(gè)例子:

            class Shape
            {
            public:
                virtual double GetArea()=0;
                virtual bool HitTest(Point p)=0;
            };
            
            class Circle : public Shape ...;
            class Rectangle : public Shape ... ;

            我們每當(dāng)添加一個(gè)新形狀的時(shí)候,只要實(shí)現(xiàn)GetArea和HitTest,那么事情就做完了。所以你可以無限的添加新形狀——所以類型擴(kuò)展是開放的。但是你卻永遠(yuǎn)只能做GetArea和HitTest——對邏輯擴(kuò)展是封閉的。你如果想做除了GetArea和HitTest以外的更多的事情的話,這個(gè)時(shí)候你就被迫做cast了。那么在類型相對穩(wěn)定的情況下有沒有別的方法呢?設(shè)計(jì)模式告訴我們,我們可以用Visitor來把情況扭轉(zhuǎn)過來——做成對類型擴(kuò)展封閉,而對邏輯擴(kuò)展開放的:

            class IShapeVisitor
            {
            public:
                virtual void Visit(Circle* shape)=0;
                virtual void Visit(Rectangle* shape)=0;
            };
            
            class Shape
            {
            public:
                virtual void Accept(IShapeVisitor* visitor)=0;
            };
            
            class Circle : public Shape
            {
            public:
                ...
            
                void Accept(IShapeVIsitor* visitor)override
                {
                    visitor->Visit(this);  // 因?yàn)橹剌d的關(guān)系,會(huì)調(diào)用到第一個(gè)Visit函數(shù)
                }
            };
            
            class Rectangle : public Shape
            {
            public:
                ...
            
                void Accept(IShapeVIsitor* visitor)override
                {
                    visitor->Visit(this);  // 因?yàn)橹剌d的關(guān)系,會(huì)調(diào)用到第二個(gè)Visit函數(shù)
                }
            };
            
            //------------------------------------------
            
            class GetAreaVisitor : public IShapeVisitor
            {
            public:
                double result;
            
                void Visit(Circle* shape)
                {
                    result = ...;
                }
            
                void Visit(Rectangle* shape)
                {
                    result = ...;
                }
            };
            
            class HitTestVisitor : public IShapeVisitor ...;

            這個(gè)時(shí)候GetArea可能調(diào)用起來就不是那么方便了,不過我們總是可以把它寫成一個(gè)函數(shù):

            double GetArea(Shape* shape)
            {
                GetAreaVisitor visitor;
                shape->Accept(&visitor);
                return visitor.result;
            }

            這個(gè)時(shí)候你可以隨意的做新的事情了,但是一旦需要添加新類型的時(shí)候,你需要改動(dòng)很多東西,首先是Visitor的接口,其實(shí)是讓所有的邏輯都支持新類型,這樣你就不能僅僅通過添加新代碼來擴(kuò)展新類型了。所以這就是對邏輯擴(kuò)展開放,而對類型擴(kuò)展封閉了。

            所以第一個(gè)問題就是:能不能做成類型擴(kuò)展也開放,邏輯擴(kuò)展也開放呢?在回答這個(gè)問題之前,我們先來看下一個(gè)問題。我們要對兩個(gè)Shape進(jìn)行求交,看看他們是不是有重疊在一起的部分。但是每一個(gè)具體的Shape,譬如Circle啊Rectangle啊,定義都是不一樣的,沒辦法有通用的處理辦法,所以我們只能寫3個(gè)函數(shù)了(RR, CC, CR)。如果有3各類型,那么我們就需要6個(gè)函數(shù)。如果有4個(gè)類型,那我們就需要有10個(gè)函數(shù)——才能處理所有情況。公式倒是可以一下子看出來,函數(shù)數(shù)量就等于1+2+ … +n,n等于類型的數(shù)量。

            這看起來好像是一個(gè)類型擴(kuò)展開放的問題是吧,但是實(shí)際上他只能用邏輯擴(kuò)展的方法來做。為什么呢?你看我們的一個(gè)visitor其實(shí)很像是我們對一個(gè)一個(gè)的具體類型都試一下看看shape是不是這個(gè)類型,從而做出正確的處理。不過這跟我們直接用if地方法相比有兩個(gè)優(yōu)點(diǎn):1、快;2、編譯器替你查錯(cuò)有保證。

            那實(shí)際上應(yīng)該怎么做呢?想想,我們這里有兩次“if type”。第一次針對第一個(gè)參數(shù),第二次針對第二個(gè)參數(shù)。所以我們一共需要n+1=3個(gè)visitor。寫的方法倒是不復(fù)雜,首先我們得準(zhǔn)備好RR,CC,CR三個(gè)邏輯,然后用visitor去識(shí)別類型然后調(diào)用它們:

            bool IntersectCC(Circle* s1, Circle* s2){ ... }
            bool IntersectCR(Circle* s1, Rectangle* s2){ ... }
            bool IntersectRR(Rectangle* s1, Rectangle* s2){ ... }
            // RC和CR是一樣的
            
            class IntersectWithCircleVisitor : public IShapeVisitor
            {
            public:
                Circle* s1;
                bool result;
            
                void Visit(Circle* shape)
                {
                    result=IntersectCC(s1, shape);
                }
            
                void Visit(Rectangle* shape)
                {
                    result=IntersectCR(s1, shape);
                }
            };
            
            class IntersectWithRectangleVisitor : public IShapeVisitor
            {
            public:
                Rectangle* s1;
                bool result;
            
                void Visit(Circle* shape)
                {
                    result=IntersectCR(shape, s1);
                }
            
                void Visit(Rectangle* shape)
                {
                    result=IntersectRR(s1, shape);
                }
            };
            
            class IntersectVisitor : public IShapeVisitor
            {
            public:
                bool result;
                IShape* s2;
            
                void Visit(Circle* shape)
                {
                    IntersectWithCircleVisitor visitor;
                    visitor.s1=shape;
                    s2->Accept(&visitor);
                    result=visitor.result;
                }
            
                void Visit(Rectangle* shape)
                {
                    IntersectWithRectangleVisitor visitor;
                    visitor.s1=shape;
                    s2->Accept(&visitor);
                    result=visitor.result;
                }
            };
            
            bool Intersect(Shape* s1, Shape* s2)
            {
                IntersectVisitor visitor;
                visitor.s2=s2;
                s1->Accept(&visitor);
                return visitor.result;
            }

            我覺得你們現(xiàn)在心里的想法肯定是:“我屮艸芔茻。”嗯,這種事情在物理引擎里面是經(jīng)常要碰到的。然后當(dāng)你需要添加一個(gè)新的形狀的時(shí)候,呵呵呵呵呵呵呵呵。不過這也是沒辦法的,誰讓現(xiàn)在的要求運(yùn)行時(shí)性能的面向?qū)ο笳Z言都這么做呢?

            當(dāng)然,如果在不要求性能的情況下,我們可以用ruby和它的mixin來做。至于說怎么辦,其實(shí)你們應(yīng)該發(fā)現(xiàn)了,添加一個(gè)Visitor和添加一個(gè)虛函數(shù)的感覺是差不多的。所以只要把Visitor當(dāng)成虛函數(shù)的樣子,讓Ruby給mixin一堆新的函數(shù)進(jìn)各種類型就好了。不過只有支持運(yùn)行時(shí)mixin的語言才能做到這一點(diǎn)。強(qiáng)類型語言我覺得是別想了。

            Mixin地方法倒是很直接,我們只要把每一個(gè)Visitor里面的Visit函數(shù)都給加進(jìn)去就好了,大概感覺上就類似于:

            class Shape
            {
            public:
                // Mixin的時(shí)候等價(jià)于給每一個(gè)具體的Shape類都添加下面三個(gè)虛函數(shù)的重寫
                virtual bool Intersect(Shape* s2)=0;
                virtual bool IntersectWithCircle(Circle* s1)=0;
                virtual bool IntersectWithRectangle(Rectangle* s1)=0;
            };
            
            //--------------------------------------------
            
            bool Circle::Intersect(Shape* s2)
            {
                return s2->IntersectWithCircle(this);
            }
            
            bool Rectangle::Intersect(Shape* s2)
            {
                return s2->IntersectWithRectangle(this);
            }
            
            //--------------------------------------------
            
            bool Circle::IntersectWithCircle(Circle* s1)
            {
                return IntersectCC(s1, this);
            }
            
            bool Rectangle::IntersectWithCircle(Circle* s1)
            {
                return IntersectCR(s1, this);
            }
            
            //--------------------------------------------
            
            bool Circle::IntersectWithRectangle(Rectangle* s1)
            {
                return IntersectCR(this, s1);
            }
            
            bool Rectangle::IntersectWithRectangle(Rectangle* s1)
            {
                return IntersectRR(s1, this);
            }

            這下子應(yīng)該看出來為什么我說這種方法只能用Visitor了吧,否則就要把所有類型都寫進(jìn)Shape,就會(huì)很奇怪了。如果這樣的邏輯一多,類型也有四五個(gè)的話,那每加一個(gè)邏輯就得添加一批虛函數(shù),Shape類很快就會(huì)被玩壞了。而代表邏輯的Visitor是可以放在不同的地方的,互相之間是隔離的,維護(hù)起來就會(huì)比較容易。

            那現(xiàn)在我們就要有第二個(gè)問題了:在擁有兩個(gè)“this”的情況下,我們要如何做才能把邏輯做成類型擴(kuò)展也開放,邏輯擴(kuò)展也開放呢?然后參考我們的第一個(gè)問題:能不能做成類型擴(kuò)展也開放,邏輯擴(kuò)展也開放呢?你應(yīng)該心里有數(shù)了吧,答案當(dāng)然是——不能做。

            這就是語言的極限了。面向?qū)ο蟛庞玫膕ingle dispatch的方法,能做到的東西是很有限的。情況稍微復(fù)雜那么一點(diǎn)點(diǎn)——就像上面對兩個(gè)形狀求交這種正常的問題——寫起來都這么難受。

            那呼應(yīng)一下標(biāo)題,如果我們要設(shè)計(jì)一門語言,來支持上面這種multiple dispatch,那可以怎么修改語法呢?這里面分為兩種,第一種是像C++這樣運(yùn)行時(shí)load dll不增加符號(hào)的,第二種是像C#這樣運(yùn)行時(shí)load dll會(huì)增加符號(hào)的。對于前一種,其實(shí)我們可以簡單的修改一下語法:

            bool Intersect(switch Shape* s1, switch Shape* s2);
            
            bool Intersect(case Circle* s1, case Circle* s2){ ... }
            bool Intersect(case Circle* s1, case Rectangle* s2){ ... }
            bool Intersect(case Rectangle* s1, case Circle* s2){ ... }
            bool Intersect(case Rectangle* s1, case Rectangle* s2){ ... }

            然后修改一下編譯器,把這些東西翻譯成虛函數(shù)塞回原來的Shape類里面就行了。對于第二種嘛,其實(shí)就相當(dāng)于Intersect的根節(jié)點(diǎn)、Circle和CC寫在dll1,Rectangle和CR、RC、RR寫在dll2,然后dll1運(yùn)行時(shí)把dll2給動(dòng)態(tài)地load了進(jìn)來,再之后調(diào)用Intersect的時(shí)候就好像“虛函數(shù)已經(jīng)進(jìn)去了”一樣。至于要怎么做,這個(gè)大家回去慢慢思考一下吧,啊哈哈哈。

            posted on 2013-05-24 19:08 陳梓瀚(vczh) 閱讀(11495) 評(píng)論(5)  編輯 收藏 引用 所屬分類: 啟示

            評(píng)論:
            # re: 如何設(shè)計(jì)一門語言(五)&mdash;&mdash;面向?qū)ο蠛拖l(fā)送 2013-05-24 20:29 | n00b
            SICP Exercise 2.76是類似的問題  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(五)&mdash;&mdash;面向?qū)ο蠛拖l(fā)送 2013-05-25 00:07 | rink1969
            這個(gè)說的就是Expression Problem嘛
            兩種方法其實(shí)就是代數(shù)方法和共代數(shù)方法。
            編程語言擴(kuò)展的根本問題就是新的方法和數(shù)據(jù)類型在反序列化過程中如何保證整個(gè)類型系統(tǒng)不出問題。
            要求不嚴(yán)格的話,用宏隨便搞搞,夠用就行了。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(五)&mdash;&mdash;面向?qū)ο蠛拖l(fā)送 2013-05-25 03:58 | 溪流
            學(xué)習(xí)了  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(五)&mdash;&mdash;面向?qū)ο蠛拖l(fā)送 2013-05-25 22:36 | Marvin
            面向?qū)ο蠛芎唵危皇钦Z言給做復(fù)雜了  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門語言(五)&mdash;&mdash;面向?qū)ο蠛拖l(fā)送 2013-05-26 05:58 | cscope
            哼哼,CLOS——————multimethod  回復(fù)  更多評(píng)論
              
            国产99久久久国产精品~~牛| 国产精品嫩草影院久久| 久久亚洲中文字幕精品有坂深雪| 亚洲天堂久久久| 狠狠色婷婷综合天天久久丁香| 国产综合精品久久亚洲| 无码国产69精品久久久久网站| 99久久精品免费观看国产| 热久久最新网站获取| 久久99国产精品一区二区| 久久亚洲精品国产亚洲老地址 | 久久久一本精品99久久精品66| 日本精品久久久久中文字幕8| AV无码久久久久不卡蜜桃| 久久精品不卡| 精品人妻伦九区久久AAA片69 | 三级片免费观看久久| 久久久中文字幕| 久久亚洲春色中文字幕久久久| 亚洲国产成人久久综合碰| 国产精品成人精品久久久| 2021精品国产综合久久| 久久国产欧美日韩精品| 欧美va久久久噜噜噜久久| 精品久久久中文字幕人妻| 久久99精品国产麻豆宅宅| 合区精品久久久中文字幕一区 | 久久福利青草精品资源站| 99999久久久久久亚洲| 国产精品久久久久jk制服| 精品多毛少妇人妻AV免费久久| 少妇被又大又粗又爽毛片久久黑人 | 精品国产乱码久久久久软件| 久久久久久噜噜精品免费直播| 国产午夜精品理论片久久| 久久99国产一区二区三区| 国产精品丝袜久久久久久不卡| 国产亚洲美女精品久久久| 三级片免费观看久久| 中文字幕日本人妻久久久免费| 狠狠色婷婷久久一区二区三区|