<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      如何設(shè)計一門語言(六)——exception和error code

      我一直以來對于exception的態(tài)度都是很明確的。首先exception是好的,否則就不會有絕大多數(shù)的語言都支持他了。其次,error code也沒什么問題,只是需要一個前提——你的語言得跟Haskell一樣有monad和comonad。你看Haskell就沒有exception,大家也寫的很開心。為什么呢?因為只要把返回帶error code結(jié)果的函數(shù)給做成一個monad/comonad,那么就可以用CPS變換把它變成exception了。所以說CPS作為跟goto同樣基本的控制流語句真是當(dāng)之無愧呀,只是CPS是type rich的,goto是type poor的。

      其實很多人對于exception的恐懼心理在于你不知道一個函數(shù)會拋什么exception出來,然后程序一crash你就傻逼了。對于server來講情況還好,出了問題只要殺掉快速重啟就行了,如今沒個replication和fault tolerance還有臉說你在寫后端(所以不知道那些做web的人究竟在反對什么)?這主要的問題還是在于client。只要client上面的東西還沒保存,那你一crash數(shù)據(jù)就完蛋了是不是——當(dāng)然這只是你的想象啦,其實根本不是這樣子的。

      我們的程序拋了一個access violation出來,和拋了其它exception出來,究竟有什么區(qū)別呢?access violation是一個很奇妙的東西,一旦拋了出來就告訴你你的程序沒救了,繼續(xù)執(zhí)行下去說不定還會有破壞作用。特別是對于C/C++/Delphi這類語言來說,你不小心把錯誤的東西寫進(jìn)了什么亂七八糟的指針里面去,那會兒什么事情都沒發(fā)生,結(jié)果程序跑著跑著就錯了。因為你那個算錯了得到的野指針,說不定是隔壁的不知道什么object的成員變量,說不定是heap里面的數(shù)據(jù)結(jié)構(gòu),或者說別的什么東西,就這么給你寫了。如果你寫了別的object的成員變量那封裝肯定就不管用了,這個類的不變量就給你破壞了。既然你的成員函數(shù)都是基于不變量來寫的,那這個時候出錯時必須的。如果你寫到了heap的數(shù)據(jù)結(jié)構(gòu)那就更加呵呵呵了,說不定下次一new就崩了,而且你還不知道為什么。

      出了access violation以外的exception基本是沒什么危害的,最嚴(yán)重的大概也就是網(wǎng)線被拔了,另一塊不是裝OS的硬盤突然壞了什么的這種反正你也沒辦法但是好歹還可以處理的事情。如果這些exception是你自己拋出來的那就更可靠了——那都是計劃內(nèi)的。只要程序未來不會進(jìn)入access violation的狀態(tài),那證明你現(xiàn)在所能拿到的所有變量,還有指針指向的memory,基本上都還是靠譜的。出了你救不了的錯誤,至少你還可以吧數(shù)據(jù)安全的保存下來,然后讓自己重啟——就跟word一樣。但是你有可能會說,拿出了access violation怎么就不能保存數(shù)據(jù)了呢?因為這個時候內(nèi)存都?xì)Я耍覆欢惚4鏀?shù)據(jù)的代碼new點東西然后掛了,這基本上是沒準(zhǔn)的。

      所以無論你喜歡exception還是喜歡error code,你所希望達(dá)到的效果本質(zhì)上就是避免程序未來會進(jìn)入access violation的狀態(tài)。想做到這一點,方法也是很簡單粗暴的——只要你在函數(shù)里面把運行前該對函數(shù)做的檢查都查一遍就好了。這個無論你用exception還是用error code,寫起來都是一樣的。區(qū)別在于調(diào)用你的函數(shù)的那個人會怎么樣。那么我來舉個例子,譬如說你覺得STL的map實在是太傻比了,于是你自己寫了一個,然后有了一個這樣子的函數(shù):

      // exception版本
      Symbol* SymbolMap::Lookup(const wstring& name);
      
      // error code版本
      int SymbolMap::Lookup(const wstring& name, Symbol*& result);
      
      // 其實COM就是你們最喜歡的error code風(fēng)格了,寫起來應(yīng)該很開心才對呀,你們的雙重標(biāo)準(zhǔn)真嚴(yán)重
      HRESULT ISymbolMap::Lookup(BSTR name, ISymbol** result);

      于是拿到了Lookup函數(shù)之后,我們就要開始來完成一個任務(wù)了,譬如說拿兩個key得到兩個symbol然后組合出一個新的symbol。函數(shù)的錯誤處理邏輯是這樣的,如果key失敗了,因為業(yè)務(wù)的原因,我們要告訴函數(shù)外面說key不存在的。調(diào)用了一個ComposeSymbol的函數(shù)丟出什么IndexOutOfRangeException顯然是不合理的。但是合并的那一步,因為業(yè)務(wù)都在同一個領(lǐng)域內(nèi),所以suppose里面的異常外面是可以接受的。如果出現(xiàn)了計劃外的異常,那我們是處理不了的,只能丟給上面了,外面的代碼對于不認(rèn)識的異常只需要報告任務(wù)失敗了就可以了。于是我們的函數(shù)就會這么寫:

      Symbol* ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map)
      {
          Symbol* sa=0;
          Symbol* sb=0;
          try
          {
              sa=map->Lookup(a);
              sa=map->Lookup(b);
          }
          catch(const IndexOutOfRangeException& ex)
          {
              throw SymbolKeyException(ex.GetIndex());
          }
          return CreatePairSymbol(sa, sb);
      }

      看起來還挺不錯?,F(xiàn)在我們可以開始考慮error code的版本了。于是我們需要思考幾個問題。首先第一個就是Lookup失敗的時候要怎么報告?直接報告key的內(nèi)容是不可能的,因為error code是個int。

      題外話,error code當(dāng)然可以是別的什么東西,如果需要返回豐富內(nèi)容的錯誤的話,那怎樣都得是一個指針了,這個時候你們就會面臨下面的問題——這已經(jīng)他媽不滿足誰構(gòu)造誰釋放的原則了呀,而且我這個指針究竟直接返回出去外面理不理呢,如果只要有一個環(huán)節(jié)不理了,那內(nèi)存豈不是泄露了?如果我要求把錯誤返回在參數(shù)里面的話,我每次調(diào)用函數(shù)都要創(chuàng)建出那么個結(jié)構(gòu)來保存異常,不僅有if的復(fù)雜度,還有創(chuàng)建空間的復(fù)雜度,整個代碼都變成了屎。所以還是老老實實用int吧……

      那我們要如何把key的信息給編碼在一個int里面呢?因為key要么是來自于a,要么是來自于b,所以其實我們就需要兩個code了。那Lookup的其他錯誤怎么辦呢?CreatePairSymbol的錯誤怎么辦呢?萬一Lookup除了ERROR_KEY_NOT_FOUND以外,或者是CreatePairSymbol的錯誤剛好跟a或者b的code重合了怎么辦?對于這個問題,我只能說:

      要不你們team的人先開會討論一下最后記錄在文檔里面?zhèn)洳橐悦夂竺娴娜丝戳松笛哿恕?/font>

      好了,現(xiàn)在假設(shè)說會議取得了圓滿成功,會議雙方加深了互相的理解,促進(jìn)了溝通,最后還寫了一個白皮書出來,有效的落實了對a和b的code的指導(dǎo),于是我們終于可以寫出下面的代碼了:

      #define SUCCESS 0 // global error code for success
      #define ERROR_COMPOSE_SYMBOL_WRONG_A 1
      #define ERROR_COMPOSE_SYMBOL_WRONG_B 2
      
      int ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map, Symbol*& result)
      {
          int code=SUCCESS;
          Symbol* sa=0;
          Symbol* sb=0;
          switch(code=map->Lookup(a, sa))
          {
          case SUCCESS:
              break;
          case ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
              return ERROR_COMPOSE_SYMBOL_WRONG_A;
          default:
              return code;
          }
          switch(code=map->Lookup(b, sb))
          {
          case SUCCESS:
              break;
          case ERROR_SYMBOL_MAP_KEY_NOT_FOUND:
              return ERROR_COMPOSE_SYMBOL_WRONG_B;
          default:
              return code;
          }
          return CreatePairSymbol(sa, sb, result);
      }

      啊,好像太長,干脆我還是不負(fù)責(zé)任一點吧,反正代碼寫的好也漲不了工資,干脆不認(rèn)識的錯誤都返回ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR好了,于是就可以把代碼變成下面這樣……都到這份上了不要叫自己程序員了,叫程序狗吧……

      #define SUCCESS 0 // global error code for success
      #define ERROR_COMPOSE_SYMBOL_WRONG_A 1
      #define ERROR_COMPOSE_SYMBOL_WRONG_B 2
      #define ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR 3
      
      int ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map, Symbol*& result)
      {
          Symbol* sa=0;
          Symbol* sb=0;
          if(map->Lookup(a, sa)!=SUCCESS)
              return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
          if(map->Lookup(b, sb)!=SUCCESS)
              return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
          if(CreatePairSymbol(sa, sb, result)!=SUCCESS)
              return ERROR_COMPOSE_SYMBOL_UNKNOWN_ERROR;
          return SUCCESS;
      }

      當(dāng)然,如果大家都一樣不負(fù)責(zé)任的話,還是exception完爆error code:

      Symbol* ComposeSymbol(const wstring& a, const wstring& b, SymbolMap* map)
      {
          return CreatePairSymbol(map->Lookup(a), map->Lookup(b));
      }

      大部分人人只會用在當(dāng)前條件下最容易寫的方法來設(shè)計軟件,而不是先設(shè)計出軟件然后再看看怎樣寫比較容易,這就是為什么我說,只要你一個月給程序員還給不到一狗半,還是老老實實在政策上落實exception吧。至少exception寫起來還不會讓人那么心煩,可以把程序?qū)懙脠怨桃稽c。

      好了,單線程下面至少你還可以爭吵說究竟exception好還是error code好,但是到了異步程序里面就完全不一樣了?,F(xiàn)在的異步程序都很多,譬如說有良心的手機(jī)app啦,譬如說javascript啦,metro程序等等。一個try根本沒辦法跨線程使用所以一個這樣子的函數(shù)(下面開始用C#,C++11的future/promise我用的還不熟):

      class Normal
      {
          public string Do(string args);
      }

      最后就會變成這樣:

      class Async
      {
          // before .NET 4.0
          IAsyncResult BeginDo(string args, Action<IAsyncResult> continuation);
          string EndDo(IAsyncResult ar);
      
          // after .NET 4.0
          Task<string> DoAsync(string args);
      }

      當(dāng)你使用BeginDo的時候,你可以在continuation里面調(diào)用EndDo,然后得到一個string,或者得到一個exception。但是因為EndDo的exception不是在BeginDo里面throw出來的,所以無論你EndDo返回string也好,返回Tuple<string, Exception>也好,對于BeginDo和EndDo的實現(xiàn)來說其實都一樣,沒有上文所說的exception和error code的區(qū)別。

      不過.NET從BeginDo/EndDo到DoAsync經(jīng)歷了一個巨大的進(jìn)步。雖然形式上都一樣,但是由于C#并不像Haskell那樣可以完美的操作函數(shù),C#還是面向?qū)ο笞龅酶茫谑侨绻覀儼蒚ask<T>看成下面的樣子,那其實兩種寫法是沒有區(qū)別的:

      class Task<T>
      {
          public IAsyncResult BeginRun(Action<IAsyncResult> continuation);
          public T EndRun(IAsyncResult ar);
      }

      不過如果還是用BeginRun/EndRun這種方法來調(diào)用的話,使用起來還是很不方便,而且也很難把更多的Task組合在一起。所以最后.NET給出的Task是下面這個樣子的(Comonad!):

      class Task<T>
      {
          public Task<U> ContinueWith<U>(Func<Task<T>, U> continuation);
      }

      盡管真實的Task<T>要比上面那個復(fù)雜得多,但是總的來說其實就是圍繞著基本簡單的函數(shù)建立起來的一大堆helper function。到這里C#終于把CPS變換在異步處理上的應(yīng)用的這一部分給抽象出來了。在看CPS的效果之前,我們先來看一個同步函數(shù):

      void button1_Clicked(object sender, EventArgs e)
      {
              // 假設(shè)我們有string Http.Download(string url);
              try
              {
                      string a = Http.Download(url1);
                      string b = Http.Download(url2);
                      textBox1.Text=a+b;
              }
              catch(Exception ex)
              {
                      textBox1.Text=ex.Message;
              }
      }

      這段代碼顯然是一個GUI里面的代碼。我們?nèi)绻谝粋€GUI程序里面這么寫,就會把程序?qū)懙酶鶴Q一樣卡了。所以實際上這么做是不對的。不過為了表達(dá)程序需要做的所有事情,就有了這么一個同步的版本。那么我們嘗試吧這個東西修改成異步的把!

      void button2_Clicked(object sender, EventArgs e)
      {
          // 假設(shè)我們有Task<string> Http.DownloadAsync(string url);
          // 需要MethodInvoker是因為,對textBox1.Text的修改只能在GUI線程里面做
          Http.DownloadAsync(url1).ContinueWith(ta=>new MethodInvoker(()=>
          {
              try
              {
                  // 這個時候ta已經(jīng)運行完了,所以對ta.Result的取值不會造成GUI線程等待IO。
                  // 而且如果DownloadAsync內(nèi)部出了錯,異常會在這里拋出來。
                  string a=ta.Result;
                  Http.DownloadAsync(url2).ContinueWith(tb=>new MethodInvoker(()=>
                  {
                      try
                      {
                          string b=tb.Result;
                          textBox1.Text=a+b;
                      }
                      catch(Exception ex)
                      {
                          textBox1.Text=ex.Message;
                      }
                  })));
              }
              catch(Exception ex)
              {
                  textBox1.Text=ex.Message;
              }
          })));
      }

      我們發(fā)現(xiàn),異步操作發(fā)生的異常,把優(yōu)越的exception拉低到了丑陋的error code的同一個情況上面——我們需要不斷地對每一個操作重復(fù)同樣的錯誤處理過程!而且在這種地方我們連“不負(fù)責(zé)任”的選項都沒有了,如果你不try-catch(或者不檢查error code),那到時候程序就會發(fā)生一些莫名其妙的問題,在GUI那一層你什么事情都不知道,整個程序就變成了傻逼。

      現(xiàn)在可以開始解釋一下什么是CPS變換了。CPS變換就是把所有g(shù)(f(x))都給改寫成f(x, r=>g(r))的過程。通俗一點講,CPS變換就是幫你把那個同步的button1_Click給改寫成異步的button2_Click的這個過程。盡管這么說可能不太嚴(yán)謹(jǐn),因為button1_Click跟button2_Click所做的事情是不一樣的,一個會讓GUI卡成qq,另一個不會。但是我們討論CPS變換的時候,我們討論的是對代碼結(jié)構(gòu)的變換,而不是別的什么東西。

      現(xiàn)在就是激動人心的一步了。既然CPS可以把返回值變換成lambda表達(dá)式,那反過來我們也可以把所有的以這種形式存在的lambda表達(dá)式都改寫成返回值嘛?,F(xiàn)在我們滾回去看一看button2_Click,會發(fā)現(xiàn)這個程序其實充滿了下面的pattern:

      // lambda的參數(shù)名字故意起了跟前面的變量一樣的名字(previousTask)因為其實他們就是同一個東西
      previousTask.ContinueWith(previousTask=>new MethodInvoker(()=>
      {
          try
          {
              continuation(previousTask.Result);
          }
          catch(Exception ex)
          {
              textBox1.Text=ex.Message;
          }
      })));

      我們可以“發(fā)明”一個語法來代表這個過程。C#用的是await關(guān)鍵字,那我們也來用await關(guān)鍵字。假設(shè)說上面的代碼永遠(yuǎn)等價于下面的這個代碼:

      try
      {
          var result=await previousTask;
          continuation(result);
      }
      catch(Exception ex)
      {
          textBox1.Text=ex.Message;
      }

      兩段代碼的關(guān)系就跟i++;和i=i+1;一樣是可以互相替換的,只是不同的寫法而已。那我們就可以用相同的方法來把button2_Click給替換成下面的button3_Click了:

      void button3_Click(object sender, EventArgs e)
      {
          try
          {
              var a=await Http.DownloadAsync(url1);
              try
              {
                  var b=await Http.DownloadAsync(url2);
                  textBox1.Text=a+b;
              }
              catch(Exception ex)
              {
                  textBox1.Text=ex.Message;
              }
          }
          catch(Exception ex)
          {
              textBox1.Text=ex.Message;
          }
      }

      聰明的讀者立刻就想到了,兩個try其實是重復(fù)的,那為什么不把他們合并成一個呢!當(dāng)然我想告訴大家的是,異常是在不同的線程里面拋出來的,只是我們用CPS變換把代碼“改寫”成這種形式而已。理論上兩個try是不能合并的。但是!我們的C#編譯器君是很聰明的。正所謂語言的抽象高級了一點,那么編譯器對你的代碼也就理解得更多了一點。如果編譯器發(fā)現(xiàn)你在try里面寫了兩個await,馬上就明白了過來他需要幫你復(fù)制catch的部分——或者說他可以幫你自動的復(fù)制catch的部分,那情況就完全不同了,最后就可以寫成:

      // C#要求函數(shù)前面要加一個async來允許你在函數(shù)內(nèi)使用await
      // 當(dāng)然同時你的函數(shù)也就返回Task而不是void了
      // 不過沒關(guān)系,C#的event也可以接受一個標(biāo)記了async的函數(shù),盡管返回值不一樣
      // 設(shè)計語言這種事情就是牽一發(fā)而動全身呀,加個await連event都要改
      async void button4_Click(object sender, EventArgs e)
      {
          try
          {
              string a=await Http.DownloadAsync(url1);
              string b=await Http.DownloadAsync(url2);
              textBox1.Text=a+b;
          }
          catch(Exception ex)
          {
              textBox1.Text=ex.Message;
          }
      }

      把兩個await換成回調(diào)已經(jīng)讓我們寫的夠辛苦了,那么如果我們把a(bǔ)wait寫在了循環(huán)里面,事情就不那么簡單了。CPS需要把循環(huán)翻譯成遞歸,那你就得把lambda表達(dá)時拿出來寫成一個普通的函數(shù)——這樣他就可以有名字了——然后才能遞歸(寫出一個用于CPS的Y-combinator是一件很困難的事情,盡管并沒有比Y-combinator本身困難多少)。這個例子就復(fù)雜到爆炸了,我在這里就不演示了。

      總而言之,C#因為有了CPS變換(await),就可以把button4_Click幫你寫成button3_Click然后再幫你寫成button2_Click,最后把整個函數(shù)變成異步和回調(diào)的形式(真正的做法要更聰明一點,大家可以反編譯去看)在異步回調(diào)的寫法里面,exception和error code其實是一樣的。但是CPS+exception和CPS+error code就跟單線程下面的exception和error code一樣,有著重大的區(qū)別。這就是為什么文章一開始會說,我只會在帶CPS變換的語言(Haskell/F#/etc)里面使用error code。

      在這類語言里面利用相同的技巧,就可以不是異步的東西也用CPS包裝起來,譬如說monadic parser combinator。至于你要選擇monad還是comonad,基本上就是取決于你要自動提供錯誤處理還是要手動提供錯誤處理。像上面的Task.ContinueWith,是要求你手動提供錯誤處理的(因為你catch了之后可以干別的事情,Task無法自動替你選擇最好的措施),所以他就把Task.ContinueWith寫成了comonad的那個樣子。

      寫到這里,不禁要同情寫前端的那幫javascript和自以為可以寫后端的node.js愛好者們,你們因為小小的eval的問題,不用老趙的windjs(windjs給javascript加上了await但是它不是一個altjs所以得顯式調(diào)用eval),是一個多大的損失……

      posted on 2013-06-10 15:02  陳梓瀚(vczh)  閱讀(3508)  評論(1)    收藏  舉報

      主站蜘蛛池模板: 亚洲一二三区精品美妇| 女的被弄到高潮娇喘喷水视频| 精品亚洲国产成人av| 国产精品成人午夜久久| 日韩精品一区二区三区激情视频| 黑巨人与欧美精品一区| 亚洲 中文 欧美 日韩 在线| 免费看亚洲一区二区三区| 无码福利写真片视频在线播放| 日韩中文字幕人妻精品| av偷拍亚洲一区二区三区| 日本丰满少妇高潮呻吟| 亚洲欧美一区二区成人片| 亚洲精品成人片在线播放| 激情文学一区二区国产区| 四虎精品视频永久免费| 国产精品成人中文字幕| 亚洲最大福利视频网| av无码精品一区二区乱子| 91精品久久一区二区三区| 国产精品办公室沙发| 黑人精品一区二区三区不| 亚洲天堂精品一区二区| 久久99精品久久久久久9| 高级艳妇交换俱乐部小说 | 亚洲国产精品高清久久久| 青青热在线精品视频免费观看| 国产亚洲一在无在线观看| 国产精品久久久一区二区| 吉川爱美一区二区三区视频| 亚洲精品无码日韩国产不卡av| 国产精品午夜剧场免费观看| 亚洲黄色片一区二区三区| 无码国内精品人妻少妇| 欧美熟妇xxxxx欧美老妇不卡| 杭锦后旗| 男人的天堂av社区在线| 国产强奷在线播放免费| 毛片无遮挡高清免费| 尹人香蕉久久99天天拍| 东乡|