日韩性视频-久久久蜜桃-www中文字幕-在线中文字幕av-亚洲欧美一区二区三区四区-撸久久-香蕉视频一区-久久无码精品丰满人妻-国产高潮av-激情福利社-日韩av网址大全-国产精品久久999-日本五十路在线-性欧美在线-久久99精品波多结衣一区-男女午夜免费视频-黑人极品ⅴideos精品欧美棵-人人妻人人澡人人爽精品欧美一区-日韩一区在线看-欧美a级在线免费观看

歡迎訪問(wèn) 生活随笔!

生活随笔

當(dāng)前位置: 首頁(yè) > 编程资源 > 编程问答 >内容正文

编程问答

如何设计一门语言(八)——异步编程和CPS变换

發(fā)布時(shí)間:2025/3/21 编程问答 19 豆豆
生活随笔 收集整理的這篇文章主要介紹了 如何设计一门语言(八)——异步编程和CPS变换 小編覺(jué)得挺不錯(cuò)的,現(xiàn)在分享給大家,幫大家做個(gè)參考.

關(guān)于這個(gè)話題,其實(shí)在(六)里面已經(jīng)討論了一半了。學(xué)過(guò)Haskell的都知道,這個(gè)世界上很多東西都可以用monad和comonad來(lái)把一些復(fù)雜的代碼給抽象成簡(jiǎn)單的、一看就懂的形式。他們的區(qū)別,就像用js做一個(gè)復(fù)雜的帶著幾層循環(huán)的動(dòng)畫(huà),直接寫(xiě)出來(lái)和用jquery的“回調(diào)”寫(xiě)出來(lái)的代碼一樣。前者能看不能用,后者能用不能看。那有沒(méi)有什么又能用又能看的呢?我目前只能在Haskell、C#和F#里面看到。至于說(shuō)為什么,當(dāng)然是因?yàn)樗麄兌贾С至薽onad和comonad。只不過(guò)C#作為一門(mén)不把“用庫(kù)來(lái)改造語(yǔ)言”作為重要特征的語(yǔ)言,并沒(méi)打算讓你們能跟haskell和F#一樣,把東西抽象成monad,然后輕松的寫(xiě)出來(lái)。C#只內(nèi)置了yield return和async await這樣的東西。

把“用庫(kù)來(lái)改造語(yǔ)言”作為重要特征的語(yǔ)言其實(shí)也不多,大家熟悉的也就只有l(wèi)isp和C++,不熟悉的有F#。F#除了computation expression以外,還有一個(gè)type provider的功能。就是你可以在你的當(dāng)前的程序里面,寫(xiě)一小段代碼,通知編譯器在編譯你的代碼的時(shí)候執(zhí)行以下(有點(diǎn)類(lèi)似雞生蛋的問(wèn)題但其實(shí)不是)。這段代碼可以生成新的代碼(而不是跟lisp一樣修改已有的代碼),然后給你剩下的那部分程序使用。例子我就不舉了,有興趣的大家看這里:http://msdn.microsoft.com/en-us/library/vstudio/hh361034.aspx。里面有一個(gè)例子講的是如何在F#里面創(chuàng)造一個(gè)強(qiáng)類(lèi)型的正則表達(dá)式庫(kù),而且并不像boost的spirit或者xpress那樣,正則表達(dá)式仍然使用字符串來(lái)寫(xiě)的。這個(gè)正則表達(dá)式在編譯的時(shí)候就可以知道你有沒(méi)有弄錯(cuò)東西了,不需要等到運(yùn)行才知道。

Haskell和F#分別嘗試了monad/comonad和computation expression,為的就是能用一種不會(huì)失控(lisp的macro就屬于會(huì)失控的那種)方法來(lái)讓用戶自己表達(dá)屬于自己的可以天然被continuation passing style變換處理的東西。在介紹C#的async await的強(qiáng)大能力之前,先來(lái)講一下Haskell和F#的做法。為什么按照這個(gè)程序呢,因?yàn)镠askell的monad表達(dá)能力最低,其次是F#,最后是C#的那個(gè)。當(dāng)然C#并不打算讓你自己寫(xiě)一個(gè)支持CPS變換的類(lèi)型。作為補(bǔ)充,我將在這篇文章的最后,講一下我最近正在設(shè)計(jì)的一門(mén)語(yǔ)言,是如何把C#的yield return和async await都變成庫(kù),而不是編譯器的功能的

下面我將拋棄所有跟學(xué)術(shù)有關(guān)的內(nèi)容,只會(huì)留下跟實(shí)際開(kāi)發(fā)有關(guān)系的東西。

一、Haskell和Monad

Haskell面臨的問(wèn)題其實(shí)比較簡(jiǎn)單,第一是因?yàn)镠askell的程序都不能有隱式狀態(tài),第二是因?yàn)镠askell沒(méi)有語(yǔ)句只有表達(dá)式。這意味著你所有的控制流都必須用遞歸或者CPS來(lái)做。從這個(gè)角度上來(lái)講,Monad也算是CPS的一種應(yīng)用了。于是我為了給大家解釋一下Monad是怎么運(yùn)作的,決定來(lái)炒炒冷飯,說(shuō)error code的故事。這個(gè)故事已經(jīng)在(七)里面講了,但是今天用的是Haskell,別有一番異域風(fēng)情。

大家用C/C++的時(shí)候都覺(jué)得處理起error code是個(gè)很煩人的事情吧。我也不知道為什么那些人放著exception不用,對(duì)error code那么喜歡,直到有一天,我聽(tīng)到有一個(gè)傻逼在微博上講:“error code的意思就是我可以不理他”。我終于明白了,這個(gè)人是一個(gè)真正的傻逼。不過(guò)Haskell還是很體恤這些人的,就跟耶穌一樣,凡是信他就可以的永生,傻逼也可以。可惜的是,傻逼是學(xué)不會(huì)Monad的,所以耶穌只是個(gè)傳說(shuō)。

由于Haskell沒(méi)有“引用參數(shù)”,所以所有的結(jié)果都必須出現(xiàn)在返回值里面。因此,倘若要在Haskell里面做error code,就得返回一個(gè)data。data就跟C語(yǔ)言的union一樣,區(qū)別是data是強(qiáng)類(lèi)型的,而C的union一不小心就會(huì)傻逼了:

data Unsure a = Sure a | Error string

然后給一些必要的實(shí)現(xiàn),首先是Functor:

instance Functor Unsure wherefmap f (Sure x) = Sure (f x)fmap f (Error e) = Error e

剩下的就是Monad了:

instance Monad Unsure wherereturn = Surefail = Error(Sure s) >>= f = f s(Error e) >>= f = Error e

看起來(lái)也不多,加起來(lái)才八行,就完成了error code的聲明了。當(dāng)然就這么看是看不出Monad的強(qiáng)大威力的,所以我們還需要一個(gè)代碼。譬如說(shuō),給一個(gè)數(shù)組包含了分?jǐn)?shù),然后把所有的分?jǐn)?shù)都轉(zhuǎn)換成“牛逼”、“一般”和“傻逼”,重新構(gòu)造成一個(gè)數(shù)組。一個(gè)真正的Haskell程序員,會(huì)把這個(gè)程序分解成兩半,第一半當(dāng)然是一個(gè)把分?jǐn)?shù)轉(zhuǎn)成數(shù)字的東西:

// Tag :: integer -> Unsure string Tag f = if f < 0 then Error "分?jǐn)?shù)必須在0-100之間" elseif f<60 then Sure "傻逼" elseif f<90 then Sure "一般" elseif f<=100 then Sure "牛逼" elseError "分?jǐn)?shù)必須在0-100之間"

后面就是一個(gè)循環(huán)了:

// TagAll :: [integer] -> Unsure [string] TagAll [] = [] TagAll (x:xs) = dofirst <- Tag xremains <- TagAll xsreturn first:remains

TagAll是一個(gè)循環(huán),把輸入的東西每一個(gè)都用Tag過(guò)一遍。如果有一次Tag返回失敗了,整個(gè)TagAll函數(shù)都會(huì)失敗,然后返回錯(cuò)誤。如果全部成功了,那么TagAll函數(shù)會(huì)返回整個(gè)處理后的數(shù)組。

當(dāng)然一個(gè)循環(huán)寫(xiě)成了非尾遞歸不是一個(gè)真正的Haskell程序員會(huì)做的事情,真正的Haskell程序員會(huì)把事情做成這樣(把>>=展開(kāi)之后你們可能會(huì)覺(jué)得這個(gè)函數(shù)不是尾遞歸,但是因?yàn)镠askell是call by need的,所以實(shí)際上會(huì)成為一個(gè)尾遞歸的函數(shù)):

// TagAll :: [integer] -> Unsure [string] TagAll xs = reverse $ TagAll_ xs [] whereTagAll [] ys = Sure ysTagAll (x:xs) ys = doy <- Tag xTagAll xs (y:ys)

為什么代碼里面一句“檢查T(mén)ag函數(shù)的返回值”的代碼都沒(méi)有呢?這就是Haskell的Monad的表達(dá)能力的威力所在了。Monad的使用由do關(guān)鍵字開(kāi)始,然后這個(gè)表達(dá)式可以被這么定義:

MonadExp::= "do" FragmentNotNullFragmentNotNull::= [Pattern "<-"] Expression EOL FragmentNullFragmentNull::= FragmentNotNull::= ε

意思就是說(shuō),do后面一定要有“東西”,然后這個(gè)“東西”是這么組成的:
1、第一樣要是一個(gè)a<-e這樣的東西。如果你不想給返回值命名,就省略“a<-”這部分
2、然后重復(fù)

這表達(dá)的是這樣的一個(gè)意思:
1、先做e,然后把結(jié)果保存進(jìn)a
2、然后做下面的事情

看到了沒(méi)有,“然后做下面的事情”是一個(gè)典型的continuation passing style的表達(dá)方法。但是我們可以看到,在例子里面所有的e都是Unsure T類(lèi)型的,而a相應(yīng)的必須為T(mén)。那到底是誰(shuí)做了這個(gè)轉(zhuǎn)化呢?

聰明的,哦不,正常的讀者一眼就能看出來(lái),“<-”就是調(diào)用了我們之前在上面實(shí)現(xiàn)的一個(gè)叫做“>>=”的函數(shù)了。我們首先把“e”和“然后要做的事情”這兩個(gè)參數(shù)傳進(jìn)了>>=,然后>>=去解讀e,得到a,把a當(dāng)成“然后要做的事情”的參數(shù)調(diào)用了一下。如果e解讀失敗的到了錯(cuò)誤,“然后要做的事情”自然就不做了,于是整個(gè)函數(shù)就返回錯(cuò)誤了。

Haskell一下就來(lái)尾遞歸還是略微復(fù)雜了點(diǎn),我們來(lái)寫(xiě)一個(gè)簡(jiǎn)單點(diǎn)的例子,寫(xiě)一個(gè)函數(shù)判斷一個(gè)人的三科成績(jī)里面,有多少科是牛逼的:

// Count牛逼 :: integer -> integer -> integer –> Unsure integer Count牛逼 chinese math english = doa <- Tag chineseb <- Tag mathc <- Tag englishreturn length [x | x <- [a, b, c], x == "牛逼"]

根據(jù)上文的描述,我們已經(jīng)知道,這個(gè)函數(shù)實(shí)際上會(huì)被處理成:

// Count牛逼 :: integer -> integer -> integer –> Unsure integer Count牛逼 chinese math englishTag chinese >>= \a->Tag math >>= \b->Tag english >>= \c->return length [x | x <- [a, b, c], x == "牛逼"]

>>=函數(shù)的定義是

instance Monad Unsure wherereturn = Surefail = Error (Sure s) >>= f = f s (Error e) >>= f = Error e

這是一個(gè)運(yùn)行時(shí)的pattern matching。一個(gè)對(duì)參數(shù)帶pattern matching的函數(shù)用Haskell的case of寫(xiě)出來(lái)是很難看的,所以Haskell給了這么個(gè)語(yǔ)法糖。但這個(gè)時(shí)候我們要把>>=函數(shù)展開(kāi)在我們的“Count牛逼”函數(shù)里面,就得老老實(shí)實(shí)地用case of了:

// Count牛逼 :: integer -> integer -> integer –> Unsure integer Count牛逼 chinese math englishcase Tag chinese of {Sure a -> case Tag math of {Sure b -> case Tag english of {Sure c -> Sure $ length [x | x <- [a, b, c], x == "牛逼"]Error e -> Error e}Error e -> Error e}Error e -> Error e}

是不是又回到了我們?cè)贑語(yǔ)言里面被迫做的,還有C++不喜歡用exception的人(包含一些覺(jué)得error code可以忽略的傻逼)做的,到處檢查函數(shù)返回值的事情了?我覺(jué)得只要是一個(gè)正常人,都會(huì)選擇這種寫(xiě)法的:

// Count牛逼 :: integer -> integer -> integer –> Unsure integer Count牛逼 chinese math englishTag chinese >>= \a->Tag math >>= \b->Tag english >>= \c->return length [x | x <- [a, b, c], x == "牛逼"]

于是我們用Haskell的Monad,活生生的把“每次都檢查函數(shù)返回值”的代碼壓縮到了Monad里面,然后就可以把代碼寫(xiě)成try-catch那樣的東西了。error code跟exception本來(lái)就是一樣的嘛,只是一個(gè)寫(xiě)起來(lái)復(fù)雜所以培養(yǎng)了很多覺(jué)得錯(cuò)誤可以忽略的傻逼,而一個(gè)只需要稍微訓(xùn)練一下就可以把代碼寫(xiě)的很簡(jiǎn)單罷了。

不過(guò)Haskell沒(méi)有變量,那些傻逼們可能會(huì)反駁:C/C++比Haskell復(fù)雜多了,你怎么知道exception就一定沒(méi)問(wèn)題呢?這個(gè)時(shí)候,我們就可以看F#的computation expression了。

二、F#和computation expression

F#雖然被設(shè)計(jì)成了一門(mén)函數(shù)式語(yǔ)言,但是其骨子里還是跟C#一樣帶狀態(tài)的,而且編譯成MSIL代碼之后,可以直接讓F#和C#互相調(diào)用。一個(gè)真正的Windows程序員,從來(lái)不會(huì)拘泥于讓一個(gè)工程只用一個(gè)語(yǔ)言來(lái)寫(xiě),而是不同的大模塊,用其適合的最好的語(yǔ)言。微軟把所有的東西都設(shè)計(jì)成可以強(qiáng)類(lèi)型地互操作的,所以在Windows上面從來(lái)不存在什么“如果我用A語(yǔ)言寫(xiě)了,B就用不了”的這些事情。這是跟Linux的一個(gè)巨大的區(qū)別。Linux是沒(méi)有強(qiáng)類(lèi)型的互操作的(字符串信仰者們?cè)僖?jiàn)),而Windows有。什么,Windows不能用來(lái)做Server?那Windows Azure怎么做的,bing怎么做的。什么,只有微軟才知道怎么正確使用Windows Server?你們喜歡玩的EVE游戲的服務(wù)器是怎么做的呢?

在這里順便黑一下gcc。錢(qián)(區(qū)別于財(cái)產(chǎn))對(duì)于一個(gè)程序員是很重要的。VC++和clang/LLVM都是領(lǐng)著工資寫(xiě)的,gcc不知道是誰(shuí)投資的(這也就意味著寫(xiě)得好也漲不了工資)。而且我們也都知道,gcc在windows上編譯的慢出來(lái)的代碼還不如VC++,gcc在linux上編譯的慢還不如clang,在mac/ios上就不說(shuō)了,下一個(gè)版本的xcode根本沒(méi)有什么gcc了。理想主義者們醒醒,gcc再見(jiàn)。

為什么F#有循環(huán)?答案當(dāng)然是因?yàn)镕#有變量了。一個(gè)沒(méi)有變量的語(yǔ)言是寫(xiě)不出循環(huán)退出條件的,只能寫(xiě)出遞歸退出條件。有了循環(huán)的話,就會(huì)有各種各樣的東西,那Monad這個(gè)東西就不能很好地給“東西”建模了。于是F#本著友好的精神,既然大家都那么喜歡Monad,那他做出一個(gè)computation expression,學(xué)起來(lái)肯定就很容易了。

于是在F#下面,那個(gè)TagAll終于可以讀入一個(gè)真正的列表,寫(xiě)出一個(gè)真正的循環(huán)了:

let TagAll xs = unsure {let r = Array.create xs.length ""for i in 0 .. xs.length-1 dolet! tag = Tag xs.[i]r.[i]<-tagreturn r }

注意那個(gè)let!,其實(shí)就是Haskell里面的<-。只是因?yàn)檫@些東西放在了循環(huán)里,那么那個(gè)“Monad”表達(dá)出來(lái)就沒(méi)有Haskell的Monad那么純粹了。為了解決這個(gè)問(wèn)題,F#引入了computation expression。所以為了讓那個(gè)unsure和let!起作用,就得有下面的代碼,做一個(gè)名字叫做unsure的computation expression:

type UnsureBuilder() =member this.Bind(m, f) = match m with| Sure a -> f a| Error s -> Error smember this.For(xs, body) =unsure{match xs with| [] -> Sure ()| x::xs -> let! r = Tag xbody rreturn this.For xs body}.... // 還有很多別的東西 let unsure = new UnsureBuilder()

所以說(shuō)帶有副作用的語(yǔ)言寫(xiě)出來(lái)的代碼又長(zhǎng),不帶副作用的語(yǔ)言寫(xiě)出來(lái)的代碼又難懂,這之間很難取得一個(gè)平衡。

如果輸入的分?jǐn)?shù)數(shù)組里面有一個(gè)不在0到100的范圍內(nèi),那么for循環(huán)里面的“l(fā)et! tag = Tag xs.[i]”這句話就會(huì)引發(fā)一個(gè)錯(cuò)誤,導(dǎo)致TagAll函數(shù)失敗。這是怎么做到的?

首先,Tag引發(fā)的錯(cuò)誤是在for循環(huán)里面,也就是說(shuō),實(shí)際運(yùn)行的時(shí)候是調(diào)用UnsuerBuilder類(lèi)型的unsure.For函數(shù)來(lái)執(zhí)行這個(gè)循環(huán)的。For函數(shù)內(nèi)部使用“l(fā)et! r = Tag x”,這個(gè)時(shí)候如果失敗,那么let!調(diào)用的Bind函數(shù)就會(huì)返回Error s。于是unsure.Combine函數(shù)判斷第一個(gè)語(yǔ)句失敗了,那么接下來(lái)的語(yǔ)句“body r ; return this.For xs body”也就不執(zhí)行了,直接返回錯(cuò)誤。這個(gè)時(shí)候For函數(shù)的遞歸終止條件就產(chǎn)生作用了,由一層層的return(F#自帶尾遞歸優(yōu)化,所以那個(gè)For函數(shù)最終會(huì)被編譯成一個(gè)循環(huán))往外傳遞,導(dǎo)致最外層的For循環(huán)以Error返回值結(jié)束。TagAll里面的unsure,Combine函數(shù)看到for循環(huán)完蛋了,于是return r也不執(zhí)行了,返回錯(cuò)誤。

這個(gè)過(guò)程跟Haskell的那個(gè)版本做的事情完全是一樣的,只是由于F#多了很多語(yǔ)句,所以Monad展開(kāi)成computation expression之后,表面上看起來(lái)就會(huì)復(fù)雜很多。如果明白Haskell的Monad在干什么事情的話,F#的computation expression也是很容易就學(xué)會(huì)的。

當(dāng)然,覺(jué)得“error code可以忽略”的傻逼是沒(méi)有可能的。

三、C#的yield return和async await

如果大家已經(jīng)明白了Haskell的>>=和F#的Bind(其實(shí)也是let!)就是一回事的話,而且也明白了我上面講的如何把do和<-變成>>=的方法的話,大家應(yīng)該對(duì)CPS在實(shí)際應(yīng)用的樣子心里有數(shù)了。不過(guò),這種理解的方法實(shí)際上是相當(dāng)有限的。為什么呢?讓我們來(lái)看C#的兩個(gè)函數(shù):

IEnumerable<T> Concat(this IEnumerable<T> a, IEnumerable<T> b) {foreach(var x in a)yield return x;foreach(var x in b)yield return x; }

上面那個(gè)是關(guān)于yield return和IEnumerable<T>的例子,講的是Linq的Concat函數(shù)是怎么實(shí)現(xiàn)的。下面還有一個(gè)async await和Task<T>的例子:

async Task<T[]> SequencialExecute(this Task<T>[] tasks) {var ts = new T[tasks.Length];for(int i=0;i<tasks.Length;i++)ts[i]=await tasks[i];return ts; }

這個(gè)函數(shù)講的是,如果你有一堆Task<T>,如何構(gòu)造出一個(gè)內(nèi)容來(lái)自于異步地挨個(gè)執(zhí)行tasks里面的每個(gè)Task<T>的Task<T[]>的方法。

大家可能會(huì)注意到,C#的yield return和await的“味道”,就跟Haskell的<-和>>=、F#的Bind和let!一樣。在處理這種語(yǔ)言級(jí)別的事情的時(shí)候,千萬(wàn)不要去管代碼它實(shí)際上在干什么,這其實(shí)是次要的。最重要的是形式。什么是形式呢?也就是說(shuō),同樣一個(gè)任務(wù),是如何被不同的方法表達(dá)出來(lái)的。上面說(shuō)的“味道”就都在“表達(dá)”的這個(gè)事情上面了。

這里我就要提一個(gè)問(wèn)題了。

  • Haskell有Monad,所以我們可以給自己定義的類(lèi)型實(shí)現(xiàn)一個(gè)Monad,從而讓我們的類(lèi)型可以用do和<-來(lái)操作。
  • F#有computation expression,所以我們可以給自己定義的類(lèi)型實(shí)現(xiàn)一個(gè)computation expression,從而讓我們的類(lèi)型可以用let!來(lái)操作。
  • C#有【什么】,所以我們可以給自己定義的類(lèi)型實(shí)現(xiàn)一個(gè)【什么】,從而讓我們的類(lèi)型可以用【什么】來(lái)操作?
  • 熟悉C#的人可能很快就說(shuō)出來(lái)了,答案是Linq、Linq Provider和from in了。這篇《Monadic Parser Combinator using C# 3.0》http://blogs.msdn.com/b/lukeh/archive/2007/08/19/monadic-parser-combinators-using-c-3-0.aspx 介紹了一個(gè)如何把語(yǔ)法分析器(也就是parser)給寫(xiě)成monad,并且用Linq的from in來(lái)表達(dá)的方法。

    大家可能一下子不明白什么意思。Linq Provider和Monad是這么對(duì)應(yīng)的:

  • fmap對(duì)應(yīng)于Select
  • >>=對(duì)應(yīng)于SelectMany
  • >>= + return也對(duì)應(yīng)與Select(回憶一下Monad這個(gè)代數(shù)結(jié)構(gòu)的幾個(gè)定理,就有這么一條)
  • 然后諸如這樣的Haskell代碼:

    // Count牛逼 :: integer -> integer -> integer –> Unsure integer Count牛逼 chinese math english = doa <- Tag chineseb <- Tag mathc <- Tag englishreturn length [x | x <- [a, b, c], x == "牛逼"]

    就可以表達(dá)成:

    Unsure<int> Count牛逼(int chinese, int math, int english) {returnfrom a in Tag(chinese)from b in Tag(math)from c in Tag(english)return new int[]{a, b, c}.Where(x=>x=="牛逼").Count(); }

    不過(guò)Linq的這個(gè)表達(dá)方法跟yield return和async await一比,就有一種Monad和computation expression的感覺(jué)了。Monad只能一味的遞歸一個(gè)一個(gè)往下寫(xiě),而computation expression則還能加上分支循環(huán)異常處理什么的。C#的from in也是一樣,沒(méi)辦法表達(dá)循環(huán)異常處理等內(nèi)容。

    于是上面提到的那個(gè)問(wèn)題

    C#有【什么】,所以我們可以給自己定義的類(lèi)型實(shí)現(xiàn)一個(gè)【什么】,從而讓我們的類(lèi)型可以用【什么】來(lái)操作?

    其實(shí)并沒(méi)有回答完整。我們可以換一個(gè)角度來(lái)體味。假設(shè)IEnumerable<T>和Task<T>都是我們自己寫(xiě)的,而不是.net framework里面的內(nèi)容,那么C#究竟要加上一個(gè)什么樣的(類(lèi)似于Linq Provider的)功能,從而讓我們可以寫(xiě)出接近yield return和async await的效果的代碼呢?如果大家對(duì)我的那篇《時(shí)隔多年我又再一次體驗(yàn)了一把跟大神聊天的感覺(jué)》還有點(diǎn)印象的話,其實(shí)我當(dāng)時(shí)也對(duì)我自己提出了這么個(gè)問(wèn)題。

    我那個(gè)時(shí)候一直覺(jué)得,F#的computation expression才是正確的方向,但是我怎么搞都搞不出來(lái),所以我自己就有點(diǎn)動(dòng)搖了。于是我跑去問(wèn)了Don Syme,他很斬釘截鐵的告訴我說(shuō),computation expression是做不到那個(gè)事情的,但是需要怎么做他也沒(méi)想過(guò),讓我自己research。后來(lái)我就得到了一個(gè)結(jié)論。

    四、Koncept(我正在設(shè)計(jì)的語(yǔ)言)的yield return和async await(問(wèn)題)

    Koncept主要的特征是concept mapping和interface。這兩種東西的關(guān)系就像函數(shù)和lambda表達(dá)式、instance和class一樣,是定義和閉包的關(guān)系,所以相處起來(lái)特別自然。首先我讓函數(shù)只能輸入一個(gè)參數(shù),不過(guò)這個(gè)參數(shù)可以是一個(gè)tuple,于是f(a, b, c)實(shí)際上是f.Invoke(Tuple.Create(a, b, c))的語(yǔ)法糖。然后所有的overloading都用類(lèi)似C++的偏特化來(lái)做,于是C++11的不定模板參數(shù)(variadic template argument)在我這里就成為一個(gè)“推論”了,根本不是什么需要特殊支持就自然擁有的東西。這也是concept mapping的常用手法。最后一個(gè)跟普通語(yǔ)言巨大的變化是我刪掉了class,只留下interface。反正你們寫(xiě)lambda表達(dá)時(shí)也不會(huì)給每個(gè)閉包命名字(沒(méi)有C++11的C++除外),那為什么寫(xiě)interface就得給每一個(gè)閉包(class)命名字呢?所以我給刪去了。剩下的就是我用類(lèi)似mixin的機(jī)制可以把函數(shù)和interface什么的給mixin到普通的類(lèi)型里面去,這樣你也可以實(shí)現(xiàn)class的東西,就是寫(xiě)起特別來(lái)麻煩,于是我在語(yǔ)法上就鼓勵(lì)你不要暴露class,改為全部暴露function、concept和interface。

    不過(guò)這些都不是重點(diǎn),因?yàn)槌诉@些差異以外,其他的還是有濃郁的C#精神在里面的,所以下面在講Koncept的CPS變換的時(shí)候,我還是把它寫(xiě)成C#的樣子,Koncept長(zhǎng)什么樣子以后我再告訴你們,因?yàn)镵oncept的大部分設(shè)計(jì)都跟CPS變換是沒(méi)關(guān)系的。

    回歸正題。之前我考慮了許久,覺(jué)得F#的computation expression又特別像是一個(gè)正確的解答,但是我怎么樣都找不到一個(gè)可以把它加入Koncept地方法。這個(gè)問(wèn)題我從NativeX(這里、這里、這里和這里)的時(shí)候就一直在想了,中間兜了一個(gè)大圈,整個(gè)就是試圖山寨F#結(jié)果失敗的過(guò)程。為什么F#的computation expression模型不能用呢,歸根結(jié)底是因?yàn)?#xff0c;F#的循環(huán)沒(méi)有break和continue。C#的跳轉(zhuǎn)是自由的,不僅有break和continue,你還可以從循環(huán)里面return,甚至goto。因此一個(gè)for循環(huán)無(wú)論如何都表達(dá)不成F#的那個(gè)函數(shù):M<U> For(IEnumerable<T> container, Func<T, M<U>> body);。break、continue、return和goto沒(méi)辦法表達(dá)在類(lèi)型上。

    偉大的先知Eric Meijer告訴我們:“一個(gè)函數(shù)的類(lèi)型表達(dá)了關(guān)于函數(shù)的業(yè)務(wù)的一切”。為什么我們還要寫(xiě)函數(shù)體,是因?yàn)榫幾g器還沒(méi)有聰明到看著那個(gè)類(lèi)型就可以幫我們把代碼填充完整。所以其實(shí)當(dāng)初看著F#的computation expression的For的定義的時(shí)候,是因?yàn)槲夷X筋短路,沒(méi)有想起Eric Meijer的這句話,導(dǎo)致我浪費(fèi)了幾個(gè)月時(shí)間。當(dāng)然我到了后面也漸漸察覺(jué)到了這個(gè)事情,產(chǎn)生了動(dòng)搖,自己卻無(wú)法確定,所以去問(wèn)了Don Syme。于是,我就得到了關(guān)于這個(gè)問(wèn)題的結(jié)論的一半:在C#(其實(shí)Koncept也是)支持用戶可以自由添加的CPS變換(譬如說(shuō)用戶添加IEnumerable<T>的時(shí)候添加yield return和yield break,用戶添加Task<T>的時(shí)候添加await和return)的話,使用CPS變換的那段代碼,必須用控制流圖(control flow graph)處理完之后生成一個(gè)狀態(tài)機(jī)來(lái)做,而不能跟Haskell和F#一樣拆成一個(gè)一個(gè)的小lambda表達(dá)式。

    其實(shí)C#的yield return和async await,從一開(kāi)始就是編譯成狀態(tài)機(jī)的。只是C#沒(méi)有開(kāi)放那個(gè)功能,所以我一直以為這并不是必須的。想來(lái)微軟里面做語(yǔ)言的那幫牛逼的人還是有牛逼的道理的,一下子就可以找到問(wèn)題的正確方向,跟搞go的二流語(yǔ)言專(zhuān)家(盡管他也牛逼但是跟語(yǔ)言一點(diǎn)關(guān)系也沒(méi)有)是完全不同的。連Mozilla的Rust的設(shè)計(jì)都比go強(qiáng)一百倍。

    那另一半的問(wèn)題是什么呢?為了把問(wèn)題看得更加清楚,我們來(lái)看兩個(gè)長(zhǎng)得很像的yield return和async await的例子。為了把本質(zhì)的問(wèn)題暴露出來(lái),我決定修改yield return的語(yǔ)法:

  • 首先把yield return修改成yield
  • 其次吧yield break修改成return
  • 然后再給函數(shù)打上一個(gè)叫做seq的東西,跟async對(duì)稱(chēng),就當(dāng)他是個(gè)關(guān)鍵字
  • 給所有CPS operator加上一個(gè)感嘆號(hào),讓他變得更清楚(這里有yield、await和return)。為什么return也要加上感嘆號(hào)呢?因?yàn)槿绻覀儼蓅eq和aysnc摘掉的話,我們會(huì)發(fā)現(xiàn)return的類(lèi)型是不匹配的。所以這不是一個(gè)真的return。
  • 然后就可以來(lái)描述一個(gè)類(lèi)似Linq的TakeWhile的事情了:

    seq IEnumerable<T> TakeWhile(this IEnumerable<T> source, Predicate<T> predicate) {foreach(var x in source){if(!predicate(x))return!;yield! x} }async Task<T[]> TakeWhile(this Task<T>[] source, Predicate<T> predicate) {List<T> result=new List<T>();foreach(var t in source){var x = await! t;if(!predicate(x))return! result.ToArray();result.Add(x);}return! result.ToArray(); } 于是問(wèn)題就很清楚了。如果我們想讓用戶自己通過(guò)類(lèi)庫(kù)的方法來(lái)實(shí)現(xiàn)這些東西,那么yield和await肯定是兩個(gè)函數(shù),因?yàn)檫@是C#里面唯一可以用來(lái)寫(xiě)代碼的東西,就算看起來(lái)再奇怪,也不可能是別的。
  • seq和async到底是什么?
  • seq下面的yield和return的類(lèi)型分別是什么?
  • async下面的await和return的類(lèi)型分別是什么?
  • 其實(shí)這里還有一個(gè)謎團(tuán)。其實(shí)seq返回的東西應(yīng)該是一個(gè)IEnumerator<T>,只是因?yàn)镃#覺(jué)得IEnumerable<T>是更好地,所以你兩個(gè)都可以返回。那么,是什么機(jī)制使得,函數(shù)可以構(gòu)造出一個(gè)IEnumerable<T>,而整個(gè)狀態(tài)機(jī)是在IEnumerator<T>的MoveNext函數(shù)里面驅(qū)動(dòng)的呢?而async和Task<T>就沒(méi)有這種情況了。

    首先解答第一個(gè)問(wèn)題。因?yàn)閥ield、return和await都是函數(shù),是函數(shù)就得有個(gè)namespace,那我們可以拿seq和async做namespace。所以seq和async,設(shè)計(jì)成兩個(gè)static class也是沒(méi)有問(wèn)題的

    其次,seq的yield和return修改了某個(gè)IEnumerator<T>的狀態(tài),而async的await和return修改了某個(gè)Task<T>的狀態(tài)。而seq和async的返回值分別是IEnumerable<T>和Task<T>。因此對(duì)于一個(gè)CPS變換來(lái)說(shuō),一共需要兩個(gè)類(lèi)型,第一個(gè)是返回值,第二個(gè)是實(shí)際運(yùn)行狀態(tài)機(jī)的類(lèi)。

    第三,CPS變換還需要有一個(gè)啟動(dòng)函數(shù)。IEnumerator<T>的第一次MoveNext調(diào)用了那個(gè)啟動(dòng)函數(shù)。而Task<T>的Start調(diào)用了那個(gè)啟動(dòng)函數(shù)。啟動(dòng)函數(shù)自己維護(hù)著所有狀態(tài)機(jī)的內(nèi)容,而狀態(tài)機(jī)本身是CPS operator們看不見(jiàn)的。為什么呢?因?yàn)橐粋€(gè)狀態(tài)機(jī)也是一個(gè)類(lèi),這些狀態(tài)機(jī)類(lèi)是沒(méi)有任何公共的contract的,也就是說(shuō)無(wú)法抽象他們。因此CPS operator必須不能知道狀態(tài)機(jī)類(lèi)

    而且yield、return和await都叫CPS operator,那么他們不管是什么類(lèi)型,本身肯定看起來(lái)像一個(gè)CPS的函數(shù)。之前已經(jīng)講過(guò)了,CPS函數(shù)就是把普通函數(shù)的返回值去掉,轉(zhuǎn)而添加一個(gè)lambda表達(dá)式,用來(lái)代表“拿到返回之后的下一步計(jì)算”。

    因此總的來(lái)說(shuō),我們拿到了這四個(gè)方程,就可以得出一個(gè)解了。解可以有很多,我們選擇最簡(jiǎn)單的部分。

    那現(xiàn)在就開(kāi)始來(lái)解答上面兩個(gè)TakeWhile最終會(huì)被編譯成什么東西了。

    五、Koncept(我正在設(shè)計(jì)的語(yǔ)言)的yield return和async await(seq答案)

    首先來(lái)看seq和yield的部分。上面講到了,yield和return都是在修改某個(gè)IEnumerator<T>的狀態(tài),但是編譯器自己肯定不能知道一個(gè)合適的IEnumerator<T>是如何被創(chuàng)建出來(lái)的。所以這個(gè)類(lèi)型必須由用戶來(lái)創(chuàng)建。而為了第一次調(diào)用yield的時(shí)候就已經(jīng)有IEnumerator<T>可以用,所以CPS的啟動(dòng)函數(shù)就必須看得到那個(gè)IEnumerator<T>。但是CPS的啟動(dòng)函數(shù)又不可能去創(chuàng)建他,所以,這個(gè)IEnumerator<T>對(duì)象肯定是一個(gè)continuation的參數(shù)了。

    看,其實(shí)寫(xiě)程序都是在做推理的。盡管我們現(xiàn)在還不知道整個(gè)CPS要怎么運(yùn)作,但是隨著這些線索,我們就可以先把類(lèi)型搞出來(lái)。搞出了類(lèi)型之后,就可以來(lái)填代碼了。

  • 對(duì)于yield,yield接受了一個(gè)T,沒(méi)有返回值。一個(gè)沒(méi)有返回值的函數(shù)的continuation是什么呢?當(dāng)然就是一個(gè)沒(méi)有參數(shù)的函數(shù)了。
  • return則連輸入都沒(méi)有。
  • 而且yield和return都需要看到IEnumerator<T>。所以他們肯定有一個(gè)參數(shù)包含這個(gè)東西。
  • 那么這三個(gè)函數(shù)的類(lèi)型就都確定下來(lái)了:

    public static class seq {public static IEnumerator<T> CreateCps<T>(Action<seq_Enumerator<T>>);public static void yield<T>(seq_Enumerator<T> state, T value, Action continuation);public static void exit<T>(seq_Enumerator<T> state /*沒(méi)有輸入*/ /*exit代表return,函數(shù)結(jié)束的意思就是不會(huì)有一個(gè)continuation*/); }

    什么是seq_Enumerator<T>呢?當(dāng)然是我們那個(gè)“某個(gè)IEnumerator<T>”的真是類(lèi)型了。

    于是看著類(lèi)型,唯一可能的有意義又簡(jiǎn)單的實(shí)現(xiàn)如下:

    public class seq_Enumerable<T> : IEnumerable<T> {public Action<seq_Enumerator<T>> startContinuation;public IEnumerator<T> CreateEnumerator(){return new seq_Enumerator<T>{startContinuation=this.startContinuation)};} }public class seq_Enumerator<T> : IEnumerator<T> {public T current;bool available;Action<seq_Enumerator<T>> startContinuation;Action continuation;public T Current{get{return this.current;}}public bool MoveNext(){this.available=false;if(this.continuation==null){this.startContinuation(this);}else{this.continuation();}return this.available;} }public static class seq {public static IEnumerable<T> CreateCps<T>(Action<seq_Enumerator<T>> startContinuation){return new seq_Enumerable{startContinuation=startContinuation};}public static void yield<T>(seq_Enumeartor<T> state, T value, Action continuation){state.current=value;state.available=true;state.continuation=continuation;}public static void exit<T>(seq_Enumeartor<T> state){} }

    那么那個(gè)TakeWhile函數(shù)最終會(huì)變成:

    public class _TakeWhile<T> {seq_Enumerator<T> _controller;Action _output_continuation_0= this.RunStateMachine;int _state;IEnumerable<T> _source;IEnumerator<T> _source_enumerator;Predicate<T> _predicate;T x;public void RunStateMachine(){while(true){switch(this.state){case 0:{this._source_enumerator = this._source.CreateEnumerator();this._state=1;}break;case 1:{if(this._state_enumerator.MoveNext()){this.x=this._state_enumerator.Current;if(this._predicate(this.x)){this._state=2;var input=this.x;seq.yield(this._controller. input, this._output_continuation_0);return;}else{seq.exit(this._controller);}}else{state._state=3;}}break;case 2:{this.state=1;}break;case 3:{seq.exit(this._controller);}break;}}} }

    但是TakeWhile這個(gè)函數(shù)是真實(shí)存在的,所以他也要被改寫(xiě):

    IEnumerable<T> TakeWhile(this IEnumerable<T> source, Predicate<T> predicate) {return seq.CreateCps(controller=>{var sm = new _Where<T>{_controller=controller,_source=source,_predicate=predicate,};sm.RunStateMachine();}); }

    最終生成的TakeWhile會(huì)調(diào)用哪個(gè)CreateCps函數(shù),然后把原來(lái)的函數(shù)體經(jīng)過(guò)CFG的處理之后,得到一個(gè)狀態(tài)機(jī)。在狀態(tài)機(jī)內(nèi)所有調(diào)用CPS operator的地方(就是yield!和return!),都把“接下來(lái)的事情”當(dāng)成一個(gè)參數(shù),連同那個(gè)原本寫(xiě)上去的CPS operator的參數(shù),還有controller(在這里是seq_Enumeartor<T>)一起傳遞過(guò)去。而return是帶有特殊的寓意的,所以它調(diào)用一次exit之后,就沒(méi)有“然后——也就是continuation”了。

    現(xiàn)在回過(guò)頭來(lái)看seq類(lèi)型的聲明

    public static class seq {public static IEnumerator<T> CreateCps<T>(Action<seq_Enumerator<T>>);public static void yield<T>(seq_Enumerator<T> state, T value, Action continuation);public static void exit<T>(seq_Enumerator<T> state /*沒(méi)有輸入*/ /*exit代表return,函數(shù)結(jié)束的意思就是不會(huì)有一個(gè)continuation*/); }

    其實(shí)想一想,CPS的自然屬性決定了,基本上就只能這么定義它們的類(lèi)型。而他們的類(lèi)型唯一定義了一個(gè)最簡(jiǎn)單有效的函數(shù)體。再次感嘆一下,寫(xiě)程序就跟在做推理完全是一摸一樣的

    六、Koncept(我正在設(shè)計(jì)的語(yǔ)言)的yield return和async await(async答案)

    因?yàn)镃PS operator都是一樣的,所以在這里我給出async類(lèi)型的聲明,然后假設(shè)Task<T>的樣子長(zhǎng)的就跟C#的System.Tasks.Task<T>一摸一樣,看看大家能不能得到async下面的幾個(gè)函數(shù)的實(shí)現(xiàn),以及上面那個(gè)針對(duì)Task<T>的TakeWhile函數(shù)最終會(huì)被編譯成什么:

    public static class async {public static Task<T> CreateCps<T>(Action<FuturePromiseTask<T>> startContinuation);{/*請(qǐng)自行填補(bǔ)*/}public static void await<T>(FuturePromiseTask<T> task, Task<T> source, Action<T> continuation);{/*請(qǐng)自行填補(bǔ)*/}public static void exit<T>(FuturePromiseTask<T> task, T source); /*在這里async的return是有參數(shù)的,所以跟seq的exit不一樣*/{/*請(qǐng)自行填補(bǔ)*/} }public class FuturePromiseTask<T> : Task<T> {/*請(qǐng)自行填補(bǔ)*/ }

    from: http://www.cnblogs.com/geniusvczh/p/3219204.html

    總結(jié)

    以上是生活随笔為你收集整理的如何设计一门语言(八)——异步编程和CPS变换的全部?jī)?nèi)容,希望文章能夠幫你解決所遇到的問(wèn)題。

    如果覺(jué)得生活随笔網(wǎng)站內(nèi)容還不錯(cuò),歡迎將生活随笔推薦給好友。

    主站蜘蛛池模板: 在线免费黄色片 | xxx国产 | 性欧美ⅴideo另类hd | 亚洲精品 日韩无码 | 日本美女黄网站 | 国产精品欧美一区二区三区 | 欧美一区三区三区高中清蜜桃 | 天天操操 | 国产精品久久久久久一区二区 | 亚洲色p| 亚洲综合网av | 国产露脸无套对白在线播放 | 成人av番号网| 天天性综合| 成长快手短视频在线观看 | 天天摸夜夜爽 | 丁香婷婷一区二区三区 | 毛片日本 | 五月激情开心网 | a级无遮挡超级高清-在线观看 | 精品无码久久久久 | 国产精品九色 | 91天天操| 伊人色爱| 日本公妇乱淫免费视频一区三区 | 国产看黄网站 | 欧美第一页浮力影院 | 综合国产精品 | 国产精品久久亚洲 | 在线播放精品 | 欧美猛交免费 | 91蝌蚪在线观看 | 国语对白清晰刺激对白 | 亚洲综合一区二区三区 | 99精品在线观看视频 | 亚洲色图视频在线 | 亚洲欧洲在线看 | 奇米影视999 | 熟妇大屁股一区二区三区视频 | 婷婷国产在线 | 国产一区二区在线不卡 | 美乳人妻一区二区三区 | 不卡中文字幕在线 | av日韩不卡 | 亚洲精视频 | 综合色婷婷一区二区亚洲欧美国产 | 97国产精品久久 | 男操女视频在线观看 | 亚洲av日韩av不卡在线观看 | 欧美黑人精品一区二区不卡 | porn国产 | 色婷婷在线视频 | www.日韩精品 | 二区在线播放 | 国产在线久久久 | 麻豆视频观看 | 色av一区| 欧美国产日韩一区二区三区 | 正在播放国产一区 | 青娱乐久久 | 91国产视频在线播放 | 人人妻人人澡人人爽国产一区 | 亚洲一区二区三区四区电影 | 中文字幕亚洲不卡 | 男女污污视频在线观看 | 午夜爽爽视频 | 久久网伊人| 精品国产一区二区三区日日嗨 | 国产精品伊人久久 | 午夜免费剧场 | 天天射天天干天天操 | 在线视频欧美日韩 | 一级片免费观看 | 午夜激情福利电影 | 黑人巨大精品一区二区在线 | 国产高清av | 麻豆一区二区99久久久久 | 久久五月天av| 亚洲17p | 法国伦理少妇愉情 | 色偷偷网| 国产一区在线视频观看 | 自拍偷拍20p | 中国a级大片 | 欧美在线视频二区 | 99国产精品免费视频 | xxxx性视频 | 欧美第二页| 成人免费看片98 | 在线中文视频 | 日本wwwwwww| 大肉大捧一进一出视频 | 亚洲久久久久 | 人妻在线一区二区 | 久久人妻精品白浆国产 | ass极品水嫩小美女ass | 户外露出一区二区三区 | 丰满熟妇肥白一区二区在线 | 老司机在线精品视频 |