• <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

            其實(shí)我在寫(xiě)這個(gè)系列的第三篇文章的時(shí)候就已經(jīng)發(fā)現(xiàn),距離機(jī)器越遠(yuǎn),也就是抽象越高的概念,坑的數(shù)量是越少的。但是這并不是說(shuō),距離機(jī)器越近的概念就越強(qiáng)大或者說(shuō)越接近本質(zhì)。這是廣大的程序員對(duì)計(jì)算理論的一種誤解。大多數(shù)人理解編程的知識(shí)結(jié)構(gòu)的時(shí)候,都是用還原論來(lái)理解的,這個(gè)方法其實(shí)并沒(méi)有錯(cuò)。但問(wèn)題在于,“還原”的方法并不是唯一的。很多人覺(jué)得,反正你多高級(jí)的語(yǔ)言編譯完了無(wú)非都是機(jī)器碼嘛。但是還有另一種解釋?zhuān)銦o(wú)論多低級(jí)的語(yǔ)言編譯完了無(wú)非也就是帶CPS變換(continuation passing style)的λ-calculus程序嘛。他們是等價(jià)的,不僅能力上也是,“本質(zhì)”上也是。

            一個(gè)用CPS變換完整地處理過(guò)的λ-calculus程序長(zhǎng)的就很像一串指令。而且類(lèi)似于C++的inline操作,在這里是完全自然、安全、容易做的。那其實(shí)為什么我們的機(jī)器不發(fā)明成這樣子呢?顯然這完全跟我們想如何寫(xiě)一個(gè)程序是沒(méi)關(guān)系的。正是這種沖突讓我們有一種“概念距離機(jī)器越遠(yuǎn)運(yùn)行速度就越慢”的錯(cuò)誤的直覺(jué)。扯遠(yuǎn)了講,就算你在用一門(mén)函數(shù)式語(yǔ)言,譬如說(shuō)Haskell也好,F(xiàn)#也好,最終在運(yùn)行的時(shí)候,還是在運(yùn)行徹底編譯出來(lái)的機(jī)器碼。這些語(yǔ)言是完全不需要“模擬器”的,雖然由于各種歷史原因人們首先開(kāi)發(fā)了模擬器。當(dāng)然一個(gè)精心設(shè)計(jì)過(guò)的C程序肯定還是要比haskell快的,但是我覺(jué)得能這么干的人不多,而且大多數(shù)時(shí)候這么干都是在浪費(fèi)老板的錢(qián)而已,因?yàn)槟銈兊某绦蛟揪筒恍枰斓侥欠N份上。這種東西就跟那些做互聯(lián)網(wǎng)對(duì)于測(cè)試的想法是一樣的——有bug?發(fā)現(xiàn)了再說(shuō),先release搶市場(chǎng)。

            如果對(duì)這方面有了解的話(huà),CPS變換——也就是Lost In Stupid Parentheses-er們最喜歡的call-with-current-continuation,他的另一個(gè)名字叫call/cc——是一種跟goto一樣強(qiáng)大而且基本的控制流的做法。goto和CPS可以互相轉(zhuǎn)換不說(shuō)了,所有其它控制流都可以轉(zhuǎn)換成goto和CPS。它們兩者在這方面是不相上下的。而且既然一個(gè)完全用CPS變換處理過(guò)的程序長(zhǎng)得就像一串指令,那你說(shuō)他們的區(qū)別是什么呢?區(qū)別就是,CPS可以是強(qiáng)類(lèi)型的,而goto則永遠(yuǎn)都不可能。

            作為廢話(huà)的最后一段,我給個(gè)小例子來(lái)講什么叫“一個(gè)用CPS變換完整地處理過(guò)的λ-calculus程序長(zhǎng)的就很像一串指令”。就讓我們用a(b( x ), c( x ))這樣的一個(gè)表達(dá)式來(lái)講:
            處理前:

            a (b x) (c x)

            處理后:

            b x λa0.
            a a0 λa1.
            c x λa2.
            a1 a2

            用我們熟悉到不能再熟悉的Haskell的Monad的手法來(lái)翻譯一下其實(shí)就是:

            a0 <- b(x)
            a1 <- a(a0)
            a2 <- c(x)
            return (a1(a2))

            好了,至于上面這種形式(看起來(lái)很像SSA)是怎么被做成機(jī)器碼的,大家自己去看編譯原理吧。上面這么多廢話(huà)就是想表達(dá)一個(gè)結(jié)論:抽象并不意味著負(fù)擔(dān)。當(dāng)然,至于對(duì)程序員的智商上的要求,對(duì)某些人也是一種負(fù)擔(dān),這個(gè)我就沒(méi)辦法了,所以就不考慮他了。

            ===============廢話(huà)結(jié)束================

            模板也是這類(lèi)抽象的一種。為什么我要把標(biāo)題寫(xiě)成“坑”,只是想跟前面統(tǒng)一一下而已,其實(shí)到了模板這么高級(jí)的抽象的時(shí)候,基本上已經(jīng)沒(méi)什么坑了。當(dāng)然C++的做法就另當(dāng)別論了,而且我想那些坑你們大概一輩子也碰不到的了。那我們先從簡(jiǎn)單的講起。

            比模板更簡(jiǎn)單的東西自然就是泛型了。為什么叫他泛型?因?yàn)榉盒蛯?shí)際上就是一種復(fù)制代碼的方法,它本身是沒(méi)有推導(dǎo)能力的,所以肯定談不上什么模板了。但是在大多數(shù)情況下,泛型這么弱的抽象也已經(jīng)基本夠用了。跟泛型相關(guān)的手法大約有三個(gè)。

            第一個(gè)就是定義一個(gè)返回一個(gè)類(lèi)的函數(shù)(在這里參數(shù)是T):

            class Array<T>
            {
                public Array(int count);
                public int Count{get;}
                public T this[int index]{get; set;}
            }

            第二個(gè)就是,調(diào)用這個(gè)函數(shù),參數(shù)給他類(lèi)型,幫我們new一個(gè)類(lèi)的實(shí)例:

            var xs = new Array<int>(10);

            這其實(shí)有兩步。第一步是對(duì)函數(shù)調(diào)用Array<int>求值得到一個(gè)T,然后對(duì)new T(10)進(jìn)行求值獲得一個(gè)對(duì)象。只是剛好Array<int>的返回值也叫Array<int>,所以比較混淆視聽(tīng)。

            事情到這里還沒(méi)完。上一篇文章中我們講到,寫(xiě)一個(gè)類(lèi)是要考慮很多contract的問(wèn)題的。所以Array<T>是個(gè)什么東西呢?他至少是一個(gè)IEnumerable<T>:

            interface IEnumerable<out T>
            {
                // ...
            }
            
            class Array<T> : IEnumerable<T>
            {
                public Array(int count);
                public int Count{get;}
                public T this[int index]{get; set;}
            }

            于是有一天我們構(gòu)造了一個(gè)Array<Array<string>>的對(duì)象,然后要寫(xiě)一個(gè)函數(shù)來(lái)處理他。這個(gè)函數(shù)做的事情很簡(jiǎn)單,就是把這個(gè)二維的數(shù)組給平攤成一個(gè)一維的數(shù)組,里面所有的數(shù)組頭尾相接起來(lái)。于是根據(jù)上一篇文章的內(nèi)容,我們寫(xiě)一個(gè)接受class的函數(shù),也是要想很多contract的問(wèn)題的(面向?qū)ο缶褪锹闊┌。_@個(gè)函數(shù)需要的只是遍歷的功能,那我們完全沒(méi)有必要要求他必須是一個(gè)Array,于是我們的函數(shù)就這么寫(xiě):

            IEnumerable<T> Flatten<T>(IEnumerable<IEnumerable<T>> xss)
            {
                foreach(var xs in xss)
                    foreach(var x in xs)
                        yield return x;
            }

            或者你也可以用高級(jí)一點(diǎn)的寫(xiě)法,反正是一樣的:

            IEnumerable<T> Flatten<T>(IEnumerable<IEnumerable<T>> xss)
            {
                return xss.Aggregate(new T[], Enumerable.Concat);
            }

            有CPS變換就是好呀,沒(méi)有CPS變換的語(yǔ)言都寫(xiě)不出yield return和async await的。但是你們這些搞前端的人,特別是做nodejs的也是,都是教條主義的,覺(jué)得eval是evil,硬是把老趙的windjs(曾用名:jscex)給拒了。虧js還是一個(gè)特別適合寫(xiě)callback的語(yǔ)言呢,結(jié)果沒(méi)有$await,你們只能把一個(gè)好好的程序?qū)懗梢恢Щ鸺恕?/p>

            那現(xiàn)在問(wèn)題來(lái)了。當(dāng)我們想把Array<Array<string>>傳給Flatten的時(shí)候,我們發(fā)現(xiàn)Flatten的參數(shù)需要的是IEnumerable<IEnumerable<string>>,究竟二維的數(shù)組能不能轉(zhuǎn)成二維的迭代器呢?

            C++嘛,因?yàn)樗鼪](méi)有C#的協(xié)變和逆變的功能,所以是做不到的了。幸好我們這里用的是C# 4.0。那C#究竟是怎么做的呢?

            其實(shí)從Array<Array<string>>到IEnumerable<IEnumerable<string>>需要兩步。第一步因?yàn)锳rray繼承自IEnumerable,所以類(lèi)型變成了IEnumerable<Array<string>>。第二部就是最重要的步驟了,因?yàn)镮Enumerable<out T>的T有一個(gè)out,這就說(shuō)明,IEnumerable里面所有的T都是用在函數(shù)的返回值上的,他只生產(chǎn)T,不會(huì)消耗T。所以一個(gè)IEnumerable<子類(lèi)型>就可以轉(zhuǎn)變成IEnumerable<父類(lèi)型>,因?yàn)樽宇?lèi)型總是可以轉(zhuǎn)變成父類(lèi)型的。因此最后IEnumerable<Array<string>>就變成了IEnumerable<IEnumerable<string>>了。

            所以現(xiàn)在我們回過(guò)頭來(lái)看上面提到的泛型的三個(gè)手法
            1、定義一個(gè)輸入類(lèi)型輸出類(lèi)型的函數(shù)(class Array<T>
            2、調(diào)用這個(gè)函數(shù)來(lái)得到你想要的類(lèi)型(new Array<int>()
            3、協(xié)變和逆變((IEnumerable<IEnumerable<string>>)new Array<Array<string>>()

            所以說(shuō)白了泛型就是一個(gè)對(duì)類(lèi)型進(jìn)行操作的東西。當(dāng)然它的內(nèi)涵遠(yuǎn)遠(yuǎn)沒(méi)有模板那么豐富,但是既然討論到了對(duì)類(lèi)型的操作,我覺(jué)得我要稍微普及一下一個(gè)類(lèi)型系統(tǒng)的常識(shí)——父類(lèi)型和子類(lèi)型。

            父類(lèi)型和子類(lèi)型說(shuō)的是什么,如果我們有兩個(gè)類(lèi)型,一個(gè)T,一個(gè)U。如果一個(gè)U的值,總是可以被看成一個(gè)T的值的話(huà),那么我們就說(shuō)U是T的子類(lèi)型。我們也可以說(shuō),U是T的子集。這里我要說(shuō)一下為什么正方形是一個(gè)長(zhǎng)方形但是我們卻不能讓正方形繼承自長(zhǎng)方形呢?因?yàn)檫@個(gè)“是一個(gè)”只在只讀的時(shí)候成立。考慮了寫(xiě),正方形就不是子類(lèi)型了。

            除了類(lèi)的繼承,協(xié)變逆變以外,還有另一種父類(lèi)型和子類(lèi)型的關(guān)系——那就是模板類(lèi)型了。舉個(gè)例子,我有一個(gè)函數(shù)是這么寫(xiě)的:T Do<T>(T t)。這是一個(gè)輸入T輸出T的模板函數(shù)。那我們有一天需要一個(gè)輸入int輸出int的函數(shù)怎么辦呢?Do<int>就好了。反過(guò)來(lái)行不行呢?不行。所以delegate T F<T>(T t)就是delegate int F(int t)的子類(lèi)型。

            當(dāng)然,上面這個(gè)例子千萬(wàn)不能和函數(shù)的協(xié)變逆變混起來(lái)了。只有模板才能這么做,delegate string(string)肯定變不了delegate object(object)的。只有delegate string(object)可以通過(guò)協(xié)變+逆變同時(shí)作用變成delegate object(string)。因?yàn)閛bject和string是繼承的關(guān)系,不是模板的關(guān)系。

            泛型到這里基本上就說(shuō)完了,輪到模板了。C#的泛型產(chǎn)生的類(lèi)可以是靜態(tài)類(lèi),其實(shí)C++就更可以了,而且C++還有偏特化這種必不可缺的東西。那偏特化有什么用呢?這跟上一篇文章將面向?qū)ο蟮臅r(shí)候一樣,其中的一個(gè)很大的用處就是拿來(lái)做contract。

            interface和template(其實(shí)是concept mapping)拿來(lái)做contract的區(qū)別,在于你選擇一個(gè)具有contract的類(lèi)型的實(shí)現(xiàn)的時(shí)候,是在運(yùn)行時(shí)做的,還是在編譯時(shí)做的。其實(shí)有很多時(shí)候我們并不想,有時(shí)候也不能入侵式地給一個(gè)類(lèi)隨便添加一個(gè)新功能。我們知道,功能就是contract,contract要么是interface,要么是template。interface并不是總是可以加上去的,譬如說(shuō)對(duì)那些不能修改的類(lèi),就像string啊int什么的。而且我們也不會(huì)拿string和int做多態(tài)。這種時(shí)候我們就需要concept mapping了,然后靠類(lèi)型推導(dǎo)去選擇正確的實(shí)現(xiàn)。在C++里面,就是用template+偏特化來(lái)做了。

            上面這段話(huà)說(shuō)得好像很抽象(一點(diǎn)都不抽象!),我們還是用一個(gè)生動(dòng)的例子來(lái)做吧。譬如說(shuō)我們要做序列化和反序列化。首先我們可以讓若干個(gè)類(lèi)型支持序列化的功能。其次,只要T支持序列化,那么vector<T>啊、list<T>什么的也將自動(dòng)支持序列化的功能。這是一個(gè)遞歸的描述,所以用template來(lái)做剛剛好。但是像vector和list這種不能修改的類(lèi),或者int這種原生的類(lèi)型,我們要怎么支持序列化呢?不能修改類(lèi),那只能寫(xiě)在類(lèi)的外面,變成一個(gè)函數(shù)了。那對(duì)于vector<T>來(lái)說(shuō),他怎么知道T的序列化函數(shù)是什么呢?相信熟悉模板的讀者肯定知道正確的答案是什么了。

            首先我們要做一個(gè)空類(lèi)叫Serializable<T>,其次不斷地偏特化他們,覆蓋所有我們需要的類(lèi)型:

            template<typename T>
            struct Serializable
            {
            };
            
            template<>
            struct Serializable<int>
            {
                static int Read(istream& i);
                static void Write(ostream& o, int v);
            };
            
            template<>
            struct Serializable<wstring>
            {
                static wstring Read(istream& i);
                static void Write(ostream& o, const wstring& v);
            };
            
            template<typename T>
            struct Serializable<vector<T>>
            {
                static vector<T> Read(istream& i);  // 我們已經(jīng)有右值引用構(gòu)造函數(shù)了,不怕!
                static void Write(ostream& o, const vector<T>& v);
            };

            這里需要提到的一點(diǎn)是,當(dāng)我們使用Serializable<vector<T>>的時(shí)候,我們首先要保證T也是Serializable的。可惜的是C++并不能讓我們很好地表達(dá)這一點(diǎn),本來(lái)C++0x是有個(gè)concept mapping的標(biāo)準(zhǔn),不過(guò)后來(lái)被干掉了,我覺(jué)得永遠(yuǎn)都不會(huì)有這個(gè)東西了。但其實(shí)這并不是什么太大的事情,因?yàn)橹灰銓?xiě)錯(cuò)了,那總是會(huì)有編譯錯(cuò)誤的。

            不過(guò)go語(yǔ)言在這一點(diǎn)上就不一樣了,go沒(méi)有模板什么像樣的代碼都寫(xiě)不出就不說(shuō)了,就算他有模板,然后同時(shí)具有Read和Write的類(lèi)型都實(shí)現(xiàn)了go的一個(gè)叫做Serializable的interface的話(huà),結(jié)果又如何呢?其實(shí)這相當(dāng)于把Serializable<T>::Read(i)和Serializable<T>::Write(o, v)都改成Read(i, &v)和Write(o, v)。這樣的問(wèn)題老趙已經(jīng)在他的博客講過(guò)了,萬(wàn)一Read和Write不是你寫(xiě)的,功能跟你要的不一樣,只是碰巧有了這兩個(gè)函數(shù)怎么辦,你還能救嗎?你已經(jīng)不能救了,因?yàn)槊侄加眠^(guò)了,你想顯式地實(shí)現(xiàn)一個(gè)interface的話(huà)go又沒(méi)這個(gè)功能,于是程序就到此傻逼了,Read和Write改名吧,祝你不是項(xiàng)目快寫(xiě)完了才發(fā)現(xiàn)這個(gè)問(wèn)題。

            關(guān)于編譯錯(cuò)誤,我覺(jué)得有一個(gè)事情是很值得說(shuō)的。為什么熟悉Haskell都覺(jué)得Haskell的程序只要經(jīng)過(guò)了編譯基本上運(yùn)行就靠譜了?其實(shí)并不是Haskell的程序真的免去了調(diào)試的這一步,而是因?yàn)檫@門(mén)語(yǔ)言經(jīng)過(guò)了精心的設(shè)計(jì),把本來(lái)在運(yùn)行時(shí)才檢查的事情給轉(zhuǎn)到了編譯時(shí)。當(dāng)然這有一個(gè)不好的地方,就是我們用C語(yǔ)言來(lái)寫(xiě)一個(gè)程序的時(shí)候,雖然因?yàn)镃語(yǔ)言抽象能力太差被迫寫(xiě)的很糟糕,但是我們總可以運(yùn)行一點(diǎn)改一點(diǎn),最終讓他可以執(zhí)行。Haskell就不一樣了,只有能編譯和不能編譯兩種狀態(tài),你要不斷的修改程序,讓他可以編譯。一旦可以編譯,一般就好了。Haskell的這種特性需要淡定的程序員才能使用。

            為什么呢,因?yàn)镠askell是沒(méi)有語(yǔ)句的,所以只要你修改了函數(shù)讓他做了不一樣的事情,那么函數(shù)的類(lèi)型就會(huì)發(fā)生變化。那么所有依賴(lài)到這個(gè)函數(shù)的函數(shù)的類(lèi)型也會(huì)發(fā)生變化。如果你改錯(cuò)了,那類(lèi)型檢查就會(huì)過(guò)不去,然后你的程序就不能編譯了。Erik Meijer菊苣說(shuō)得好,函數(shù)的類(lèi)型才是表達(dá)函數(shù)業(yè)務(wù)邏輯的地方。而之所以要函數(shù)體,那是因?yàn)榫幾g器不夠聰明,得讓你告訴他滿(mǎn)足這個(gè)類(lèi)型的最簡(jiǎn)單的解是什么。

            所以如果我們?cè)贑++也采用這種寫(xiě)法的話(huà)——其實(shí)也就是把邏輯都交給template+偏特化,或者繼承+visitor來(lái)做,那么也會(huì)有一樣的效果,雖然并沒(méi)有Haskell那么嚴(yán)格。一旦你進(jìn)行了本質(zhì)上的邏輯的變動(dòng),那你的類(lèi)型一定會(huì)受到影響,那不滿(mǎn)足類(lèi)型要求的地方編譯器就會(huì)幫你找出來(lái)。所以,當(dāng)你看到一個(gè)因?yàn)檫@種情況而產(chǎn)生的編譯錯(cuò)誤的時(shí)候,心理要想:“好棒,編譯器又給我找出了一個(gè)錯(cuò)誤,避免我在運(yùn)行的時(shí)候才苦逼的調(diào)試它!

            當(dāng)然,模板的這些手法,可以很輕易地用在continuation passing style變換啊、combinator(很像設(shè)計(jì)模式但其實(shí)不是的東西)啊、async啊actor等各種強(qiáng)類(lèi)型的物體上面,不過(guò)這些東西我今天就不打算細(xì)說(shuō)了。當(dāng)我們?cè)谧鲱?lèi)似的事情的時(shí)候,我們要把類(lèi)型設(shè)計(jì)成能表達(dá)業(yè)務(wù)邏輯的那種形式,從而讓編譯器查更多的東西,把運(yùn)行時(shí)錯(cuò)誤盡量化簡(jiǎn)為編譯錯(cuò)誤。

            當(dāng)然,C++的template比concept mapping還要更牛逼一個(gè)等級(jí)的,就是可以做type traits。如果是concept mapping是在對(duì)值通過(guò)類(lèi)型進(jìn)行分類(lèi)采用不同的計(jì)算方法的話(huà),那么type traits就是用來(lái)直接對(duì)類(lèi)型進(jìn)行計(jì)算的。那什么是對(duì)類(lèi)型進(jìn)行計(jì)算呢?我來(lái)舉個(gè)例子。

            譬如說(shuō)我們要對(duì)一個(gè)vector進(jìn)行排序,但是這個(gè)時(shí)候我們不像std::sort一樣直接給出一個(gè)比較函數(shù),而是通過(guò)從T拿出一個(gè)U當(dāng)key的方法來(lái)做。譬如說(shuō)對(duì)Student類(lèi)排序,我們可以用他的學(xué)號(hào)、成績(jī)、姓名等等任何一個(gè)屬性來(lái)排序,這還是一個(gè)相當(dāng)有用的作法。當(dāng)然,我們總是可以寫(xiě)一個(gè)lambda表達(dá)式來(lái)做出一個(gè)用某個(gè)屬性來(lái)比較Student類(lèi)的函數(shù),然后讓std::sort來(lái)解決。很可惜的是,現(xiàn)在team的編譯器還不夠新,不支持C++11,怎么辦呢?于是我們決定擼一個(gè)自己的sort函數(shù)(雖然這種事情是不推薦的,但反正是個(gè)例子,就不糾結(jié)這個(gè)了):

            template<typename T, typename U>
            void Sort(vector<T>& ts, U(*key)(T))
            {
                for(int i=0;i<ts.size()-1;i++)
                    for(int j=ts.size()-1;j>i;j--)
                        if(key(ts[j-1]) > key(ts[j]))
                            swap(ts[j-1], ts[j]);
            }

            這段冒泡排序看起來(lái)沒(méi)什么問(wèn)題對(duì)吧,無(wú)論用什么語(yǔ)言最后寫(xiě)出來(lái)都肯定是這個(gè)樣子的。于是我們寫(xiě)了幾個(gè)單元測(cè)試,譬如說(shuō)用sin來(lái)對(duì)double排序啦,求個(gè)負(fù)數(shù)實(shí)現(xiàn)逆序啦等等,覺(jué)得都沒(méi)問(wèn)題。于是開(kāi)始投入實(shí)戰(zhàn)了!我們寫(xiě)了下面的代碼:

            int GetScore(const Student& st)
            {
                return st.score;
            }
            
            vector<Student> students;
            ....
            Sort(students, &GetScore); // error

            為什么會(huì)錯(cuò)呢?因?yàn)镚etScore函數(shù)接受的不是Student而是const Student&!這下可麻煩了。我們有些函數(shù)接受的是T,有些函數(shù)接受的是const T&,難道要寫(xiě)兩個(gè)Sort嘛?當(dāng)然這種代價(jià)肯定是不能接受的。于是我們想,如果可以從U(*key)(T)的T推導(dǎo)出vector里面裝的是什么那該多好啊。反正無(wú)論函數(shù)接受的是string, string& const string, const string&,vector反正只能放string的。

            這個(gè)時(shí)候就要祭出偉大的type traits了。怎么做呢?其實(shí)根上面的說(shuō)法一樣,我們把template看成是一個(gè)函數(shù),輸入是一個(gè)類(lèi)型,輸出再也不是值了,還是一個(gè)類(lèi)型就好了:

            template<typename T>
            struct RemoveCR
            {
                typedef T Result;
            };
            
            template<typename T>
            struct RemoveCR<T&>
            {
                typedef typename RemoveCR<T>::Result Result;
            };
            
            template<typename T>
            struct RemoveCR<const T>
            {
                typedef typename RemoveCR<T>::Result Result;
            };

            我們要怎么看這個(gè)過(guò)程呢?其實(shí)這是個(gè)pattern matching的過(guò)程,而pattern matching在一定程度上跟if-else其實(shí)是差不多的。所以我們看著一類(lèi)的東西,心里不要總是想著他是個(gè)模板,而是要想,RemoveCR是個(gè)函數(shù)。所以當(dāng)我們看見(jiàn)第一個(gè)RemoveCR的時(shí)候,我們心里浮現(xiàn)出來(lái)的景象大概是這個(gè)樣子的:

            Type RemoveCR(Type T)
            {
                if(false);
                ....
                else
                    return T;
            }

            好了,這個(gè)時(shí)候我們看見(jiàn)了第二個(gè)RemoveCR,那么這個(gè)RemoveCR函數(shù)又具體了一點(diǎn):

            Type RemoveCR(Type T)
            {
                if(false);
                else if(T is T0&)
                    return RemoveCR(T0);
                ....
                else
                    return T;
            }

            后來(lái)我們看到了第三個(gè)RemoveCR,發(fā)現(xiàn)定義到此結(jié)束了,于是RemoveCR函數(shù)的實(shí)際的樣子就出來(lái)了:

            Type RemoveCR(Type T)
            {
                if(false);
                else if(T is T0&)
                    return RemoveCR(T0);
                else if(T is const T0)
                    return RemoveCR(T0);
                else
                    return T;
            }

            于是我們就可以做很多驗(yàn)證了,譬如說(shuō)RemoveCR<int>::Result的結(jié)果是int,RemoveCR<const int&>::Result的結(jié)果還是int。現(xiàn)在好了,我們可以修改我們的Sort函數(shù)了:

            template<typename T, typename U>
            void Sort(vector<typename RemoveCR<T>::Result>& ts, U(*key)(T))
            {
                ....
            }

            無(wú)論你的排序函數(shù)接受的是Student還是const Student&,現(xiàn)在Sort函數(shù)都知道,你需要對(duì)vector<Student>進(jìn)行排序。于是任務(wù)就完成了!

            別的語(yǔ)言里面沒(méi)有這種問(wèn)題,是因?yàn)橹挥蠧++才會(huì)把const、volatile和&這樣的東西用來(lái)修飾一個(gè)類(lèi)型而不是一個(gè)變量。這一點(diǎn)我第二篇文章已經(jīng)講過(guò)了,就不繼續(xù)啰嗦了。所以說(shuō)C++的設(shè)計(jì)雖然考慮得很周到,Bjarne Stroustrup菊苣也說(shuō)過(guò)他不喜歡像別的語(yǔ)言的作者一樣把自己的觀(guān)點(diǎn)強(qiáng)加給用戶(hù)。從這個(gè)出發(fā)點(diǎn)上來(lái)看,C++這一點(diǎn)相當(dāng)好。只要你肯學(xué)習(xí),又不會(huì)太蠢的話(huà),總是可以學(xué)會(huì)C++的正確使用方法,正常使用C++來(lái)寫(xiě)代碼的。但是,人真的蠢怎么辦呢?Bjarne Stroustrup你這樣歧視愚蠢的程序員是不對(duì)的,難道蠢就不能做程序員嗎,難道學(xué)了go陷進(jìn)去不能自拔的人就再也沒(méi)有機(jī)會(huì)學(xué)習(xí)C++了嗎!

            關(guān)于type traits,haskell的type class(這東西就跟concept mapping一樣)其實(shí)也有一部分這樣的能力,可以幫你從type class的一部分類(lèi)型參數(shù)推導(dǎo)出另一部分的類(lèi)型參數(shù)。譬如說(shuō)你這么寫(xiě):

            class MyClass a b c : a b => c
              where ....

            那么只要你實(shí)現(xiàn)了MyClass Int String Char,就不能實(shí)現(xiàn)MyClass Int String String了,因?yàn)閍 b => c這條規(guī)則已經(jīng)限制了,(Int String)只能出現(xiàn)一次,c要完全被a和b決定。所以擁有這個(gè)=>的haskell的type class也就可以有一部分type traits的功能了,雖然用法跟C++是截然不同的。

            那C++的template除了泛型、concept和type traits之外,還有沒(méi)有別的功能呢?當(dāng)然有,我們不僅可以把類(lèi)型放進(jìn)模板的參數(shù)列表里面去,也可以把一個(gè)整數(shù)放進(jìn)去。這個(gè)時(shí)候我們就可以用Int<100>來(lái)表達(dá)100,用Pair<Int<100>, Pair<Int<200>, Pair<Int<300>, PairEnd>>>來(lái)代表數(shù)組[100, 200, 300],然后各種奇技淫巧就可以用來(lái)把模板寫(xiě)成一個(gè)不帶類(lèi)型的函數(shù)式語(yǔ)言了,在編譯期什么東西都可以算。這個(gè)事情展開(kāi)講就太復(fù)雜了,而且也沒(méi)什么用,你們感興趣的話(huà)去看《C++ Template Metaprogramming》就行了。

            posted on 2013-05-12 00:31 陳梓瀚(vczh) 閱讀(10761) 評(píng)論(11)  編輯 收藏 引用 所屬分類(lèi): 啟示

            評(píng)論:
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板)[未登錄](méi) 2013-05-12 06:13 | Korall
            啦啦啦... 本來(lái)還想著啥時(shí)候出序章,沒(méi)想一瞥便看到了標(biāo)題后面的(四),又讀了,又沒(méi)讀懂了
            啦啦啦...  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-12 06:48 | Default
            我是來(lái)賣(mài)個(gè)小萌的~  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-12 18:24 | willis
            一開(kāi)始的CPS變換沒(méi)有看懂。。。后面的C++部分倒完全看懂了  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-13 10:22 | BYVois
            不喜歡wind.js可以用continuation.js,沒(méi)有eval。 https://github.com/BYVoid/continuation/  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-13 18:10 | 陳梓瀚(vczh)
            @BYVois
            continuation.js遠(yuǎn)遠(yuǎn)不夠wind.js好用呀  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-13 19:17 | 溪流
            開(kāi)始看不大懂了= =  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-14 23:17 | GlacJAY
            Go 遇到這種情況還是可以救的,你可以定義一個(gè)新的別名類(lèi)型,然后在這個(gè)新類(lèi)型上實(shí)現(xiàn)你需要的 Read 和 Write ,然后用這個(gè)新類(lèi)型來(lái)讀寫(xiě)你原來(lái)的對(duì)象。

            比如說(shuō):

            type TypeA struct { ... }
            func (a *TypeA) Read(r io.Reader) err error { 別人的實(shí)現(xiàn)方法 }
            func (a *TypeA) Write(w io.Writer) err error { 別人的實(shí)現(xiàn)方法 }

            然后定義新的類(lèi)型:

            type TypeB TypeA
            func (b *TypeB) Read(r io.Reader) err error { 你的實(shí)現(xiàn)方法 }
            func (b *TypeB) Write(w io.Writer) err error { 你的實(shí)現(xiàn)方法 }

            然后當(dāng)你有一個(gè) TypeA 類(lèi)型的對(duì)象 objA 時(shí),就可以通過(guò) TypeB(objA) 來(lái)調(diào)用你自己的 Read 和 Write 了。對(duì)象本身并沒(méi)有變化,只是所調(diào)用的方法變了。

            這是我當(dāng)時(shí)想到這個(gè)問(wèn)題時(shí),到列表上問(wèn)到的答案。
              回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-21 06:37 | amazing
            你的編程能力這么強(qiáng),是否能利用的編譯功底,寫(xiě)一個(gè)英語(yǔ)(自然語(yǔ)言)的分析軟件,分析英語(yǔ)的文法結(jié)構(gòu),幫助一下英語(yǔ)學(xué)習(xí)者?中文的語(yǔ)法分析也可以分析一下,比如說(shuō),可以分析現(xiàn)代漢語(yǔ)的文法結(jié)構(gòu)和古漢語(yǔ)的文法結(jié)構(gòu),找出漢語(yǔ)的演變過(guò)程。另外,如果你能夠?qū)懸黄P(guān)于文字處理的詳細(xì)的文章,解釋清楚文字的輸入到顯示的整個(gè)過(guò)程,包括Unicode和字體格式(這個(gè)我嘗試過(guò)分析,可是很頭大)的處理,那對(duì)很多人可謂是福音。以你的能力應(yīng)該沒(méi)有問(wèn)題。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-21 06:50 | amazing
            漢字字符集是一個(gè)不完全的字符集,如果一個(gè)字庫(kù)只包含有部分漢字的字形,而另一個(gè)字庫(kù)只包含另一部分不同漢字的字形,在不支持對(duì)部分文字進(jìn)行字體渲染的軟件中(比如editplus這些純文本編輯器),是沒(méi)有辦法同時(shí)顯示這兩部分漢字的(Word這種軟件可以對(duì)部分文字選擇不同的字體)。可是在純文本編輯器中,選中“Arial”這種字體,為什么它能夠顯示中文。搞不懂,請(qǐng)教。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2013-05-23 23:14 | 陳梓瀚(vczh)
            @amazing
            字體會(huì)fallback的,顯然中文不可能出現(xiàn)在A(yíng)rial里面,所以中文用的是其它字體。操作系統(tǒng)有一張內(nèi)建的字體的callback優(yōu)先級(jí)列表。  回復(fù)  更多評(píng)論
              
            # re: 如何設(shè)計(jì)一門(mén)語(yǔ)言(四)&mdash;&mdash;什么是坑(操作模板) 2014-02-03 02:10 | 海邊泡沫
            @amazing
            自然語(yǔ)言分析這項(xiàng)工作有很多科學(xué)家在做。
            不過(guò)現(xiàn)在最有成效的方法不是基于文法分析的,而是基于統(tǒng)計(jì)學(xué)的。請(qǐng)看《數(shù)學(xué)之美》這本書(shū)  回復(fù)  更多評(píng)論
              
            久久无码人妻一区二区三区午夜 | 亚洲人成网站999久久久综合 | 国产精品一区二区久久精品无码| 精品久久久久久中文字幕大豆网| 精品久久久久久久中文字幕| 欧美伊香蕉久久综合类网站| 久久综合亚洲欧美成人| 亚洲色大成网站www久久九| 亚洲第一永久AV网站久久精品男人的天堂AV| 久久婷婷久久一区二区三区| 狠狠88综合久久久久综合网| 久久偷看各类wc女厕嘘嘘| 久久无码人妻一区二区三区午夜| 久久AV高潮AV无码AV| 国产色综合久久无码有码| 久久99这里只有精品国产| 久久婷婷五月综合97色直播| 久久综合亚洲鲁鲁五月天| 五月丁香综合激情六月久久 | 久久er热视频在这里精品| 久久国产精品久久精品国产| 四虎国产精品免费久久5151| 国产L精品国产亚洲区久久| 久久精品国产99久久香蕉| 青青青青久久精品国产h久久精品五福影院1421 | 一本久道久久综合狠狠爱| 久久人妻少妇嫩草AV无码专区| 99久久99这里只有免费的精品| 久久99精品国产99久久| 狠狠久久综合| 偷偷做久久久久网站| 国产精品99久久精品| 久久久久久亚洲精品无码| 无码任你躁久久久久久| 午夜天堂av天堂久久久| 日韩亚洲欧美久久久www综合网 | 青青草原综合久久大伊人导航| 香蕉久久夜色精品升级完成| 国产高潮国产高潮久久久91 | 久久婷婷色综合一区二区 | 9999国产精品欧美久久久久久|