自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

C# Actor的尷尬與F#美麗外表下的遺憾

開發(fā) 開發(fā)工具
本文從Erlang的Tag Message開始,講述C# Actor中存在的一些問題。最后作者又提到了F#的模式匹配,以及F#中存在的問題。

上一篇文章中,我們簡單解讀了Erlang在執(zhí)行消息時(shí)候的方式。而現(xiàn)在,我們就一起來看看,C# Actor究竟出現(xiàn)了什么樣的尷尬。此外,我還打算用F#進(jìn)行補(bǔ)充說明,最終我們會(huì)發(fā)現(xiàn),雖然F#看上去很美,但是在實(shí)際使用過程中依舊有些遺憾。

Erlang中的Tag Message

老趙在上一篇文章里提到,Erlang中有一個(gè)“約定俗成”,使用“原子(atom)”來表示這條消息“做什么”,并使用“綁定(binding)”來獲取做事情所需要的“參數(shù)”。Erlang大拿,《Programming Erlang》一書的主要譯者jackyz同學(xué)看了老趙的文章后指出,這一點(diǎn)在Erlang編程規(guī)范中有著明確的說法,是為“Tag Message”:

5.7 Tag messages

All messages should be tagged. This makes the order in the receive statement less important and the implementation of new messages easier.

Don’t program like this:

  1. loop(State) ->  
  2.   receive  
  3.     ...  
  4.     {Mod, Funcs, Args} -> % Don't do this 
  5.       apply(Mod, Funcs, Args},  
  6.       loop(State);  
  7.     ...  
  8.   end. 

If messages are synchronous, the return message should be tagged with a new atom, describing the returned message. Example: if the incoming message is tagged get_status_info, the returned message could be tagged status_info. One reason for choosing different tags is to make debugging easier.

This is a good solution:

  1. loop(State) ->  
  2.   receive  
  3.     ...  
  4.     {execute, Mod, Funcs, Args} -> % Use a tagged message.  
  5.       apply(Mod, Funcs, Args},  
  6.       loop(State);  
  7.     {get_status_info, From, Option} ->  
  8.       From ! {status_info, get_status_info(Option, State)},  
  9.       loop(State);      
  10.     ...  
  11.   end.  

第一段代碼使用的模式為擁有三個(gè)“綁定”的“元組”。由于Erlang的弱類型特性,任何擁有三個(gè)元素的元組都會(huì)被匹配到,這不是一個(gè)優(yōu)秀的實(shí)踐。在第二個(gè)示例中,每個(gè)模式使用一個(gè)“原子”來進(jìn)行約束,這樣可以獲取到相對(duì)具體的消息。為什么說“相對(duì)”?還是因?yàn)镋rlang的弱類型特性,Erlang無法對(duì)From和Option提出更多的描述。同樣它也無法得知execute或get_status_info這兩個(gè)tag的來源——當(dāng)然,在許多時(shí)候,它也不需要關(guān)心是誰發(fā)送給它的。

在C#中使用Tag Message

在C#中模擬Erlang里的Tag Message很簡單,其實(shí)就是把每條消息封裝為Tag和參數(shù)列表的形式。同樣的,我們使用的都是弱類型的數(shù)據(jù)——也就是object類型。如下:

  1. public class Message  
  2. {  
  3.     public object Tag { getprivate set; }  
  4.  
  5.     public ReadOnlyCollection﹤object> Arguments { getprivate set; }  
  6.  
  7.     public Message(object tag, params object[] arguments)  
  8.     {  
  9.         this.Tag = tag;  
  10.         this.Arguments = new ReadOnlyCollection﹤object>(arguments);  
  11.     }  
  12. }  

我們可以使用這種方式來實(shí)現(xiàn)一個(gè)乒乓測(cè)試。既然是Tag Message,那么定義一些Tag便是首要任務(wù)。Tag表示“做什么”,即消息的“功能”。在乒乓測(cè)試中,有兩種消息,共三個(gè)“含義”。Erlang使用原子作為tag,在.NET中我們自然可以使用枚舉:

  1. public enum PingMsg  
  2. {   
  3.     Finished,  
  4.     Ping  
  5. }  
  6.  
  7. public enum PongMsg  
  8. {   
  9.     Pong  
  10. }  

在這里,我們使用簡單的ActorLite進(jìn)行演示(請(qǐng)參考ActorLite的使用方式)。因此,Ping和Pong均繼承于Actor﹤Message>類,并實(shí)現(xiàn)其Receive方法。

對(duì)于Ping對(duì)象來說,它會(huì)維護(hù)一個(gè)計(jì)數(shù)器。每當(dāng)收到PongMsg.Pong消息后,會(huì)將計(jì)數(shù)器減1。如果計(jì)數(shù)器為0,則回復(fù)一條PingMsg.Finished消息,否則就回復(fù)一個(gè)PingMsg.Ping:

  1. public class Ping : Actor﹤Message>  
  2. {  
  3.     private int m_count;  
  4.  
  5.     public Ping(int count)  
  6.     {  
  7.         this.m_count = count;  
  8.     }  
  9.  
  10.     public void Start(Actor﹤Message> pong)  
  11.     {  
  12.         pong.Post(new Message(PingMsg.Ping, this));  
  13.     }  
  14.  
  15.     protected override void Receive(Message message)  
  16.     {  
  17.         if (message.Tag.Equals(PongMsg.Pong))  
  18.         {  
  19.             Console.WriteLine("Ping received pong");  
  20.  
  21.             var pong = message.Arguments[0] as Actor﹤Message>;  
  22.             if (--this.m_count > 0)  
  23.             {  
  24.                 pong.Post(new Message(PingMsg.Ping, this));  
  25.             }  
  26.             else 
  27.             {  
  28.                 pong.Post(new Message(PingMsg.Finished));  
  29.                 this.Exit();  
  30.             }  
  31.         }  
  32.     }  
  33. }  

對(duì)于Pong對(duì)象來說,如果接受到PingMsg.Ping消息,則回復(fù)一個(gè)PongMsg.Pong。如果接受的消息為PingMsg.Finished,便立即退出:

  1. public class Pong : Actor﹤Message>  
  2. {  
  3.     protected override void Receive(Message message)  
  4.     {  
  5.         if (message.Tag.Equals(PingMsg.Ping))  
  6.         {  
  7.             Console.WriteLine("Pong received ping");  
  8.  
  9.             var ping = message.Arguments[0] as Actor﹤Message>;  
  10.             ping.Post(new Message(PongMsg.Pong, this));  
  11.         }  
  12.         else if (message.Tag.Equals(PingMsg.Finished))  
  13.         {  
  14.             Console.WriteLine("Finished");  
  15.             this.Exit();  
  16.         }  
  17.     }  
  18. }  

啟動(dòng)乒乓測(cè)試:

new Ping(5).Start(new Pong());結(jié)果如下:

Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Pong received ping
Ping received pong
Finished

從上述代碼中可以看出,由于沒有Erlang的模式匹配,我們必須使用if…else…的方式來判斷消息的Tag,接下來還必須使用麻煩而危險(xiǎn)的cast操作來獲取參數(shù)。更令人尷尬的是,與Erlang相比,在C#中使用Tag Message沒有獲得任何好處。同樣是弱類型,同樣得不到靜態(tài)檢查。那么好處在哪里?至少我的確看不出來。

C# Actor,強(qiáng)類型與弱類型的考慮

有朋友可能會(huì)說,C#既然是一門強(qiáng)類型的語言,為什么要學(xué)Erlang的Tag Message?為什么不把Ping定義為Actor﹤PingMessage>,同時(shí)把Pong定義為Actor﹤PingMessage>呢?

呃……我承認(rèn),在這里使用Tag Message的確有種“畫虎不成反類犬”的味道。不過,事情也不是您想象的那么簡單。因?yàn)樵趯?shí)際情況中,一個(gè)Actor可能與各種外部服務(wù)打交道,它會(huì)接受到各式各樣的消息。例如,它先向Service Locator發(fā)送一個(gè)請(qǐng)求,用于查詢數(shù)據(jù)服務(wù)的位置,這樣它會(huì)接受到一個(gè)ServiceLocatorResponse消息。然后,它會(huì)向數(shù)據(jù)服務(wù)發(fā)送一個(gè)請(qǐng)求,再接受到一個(gè)DataAccessResponse消息。也就是說,很可能我們必須把每個(gè)Actor都定義為Actor﹤object>,然后對(duì)消息進(jìn)行類型判斷,轉(zhuǎn)換,再加以處理。

誠然,這種方法相對(duì)于Tag Message擁有了一定的強(qiáng)類型優(yōu)勢(shì)(如靜態(tài)檢查)。但是如果您選擇這么做,就必須為各種消息定義不同的類型,在這方面會(huì)帶來額外的開發(fā)成本。要知道,消息的數(shù)量并不等于Actor類型的數(shù)量,即使是如Ping這樣簡單的Actor,都會(huì)發(fā)送兩種不同的消息(Ping和Finished),而且每種消息擁有各自的參數(shù)。一般來說,某個(gè)Actor會(huì)接受2-3種消息都是比較正常的狀況。在面對(duì)消息類型的汪洋時(shí),您可能就會(huì)懷念Tag Message這種做法了。到時(shí)候您可能就會(huì)發(fā)牢騷說:

“弱類型就弱類型吧,Erlang不也用的好好的么……”

F#中的模式匹配

提到模式匹配,熟悉F#的同學(xué)們可能會(huì)歡喜不已。模式匹配是F#中的重要特性,它將F#中靜態(tài)類型系統(tǒng)的靈活性體現(xiàn)地淋漓盡致。而且——它還很能節(jié)省代碼(這點(diǎn)在老趙以前的文章中也有所提及)。那么我們?cè)賮砜匆淮蜦#在乒乓測(cè)試中的表現(xiàn)。

首先還是定義PingMsg和PongMsg:

  1. type PingMsg =   
  2.     | Ping of PongMsg Actor  
  3.     | Finished  
  4. and PongMsg =   
  5.     | Pong of PingMsg Actor 

這里體現(xiàn)了F#類型系統(tǒng)中的Discriminated Unions。簡單地說,它的作用是把一種類型定義為多種表現(xiàn)形式,這個(gè)特性在Haskell等編程語言中非常常見。Discriminated Unions非常適合模式匹配,現(xiàn)在的ping對(duì)象和pong對(duì)象便可定義如下(在這里還是使用了ActorLite,而不是F#標(biāo)準(zhǔn)庫中的MailboxProcessor來實(shí)現(xiàn)Actor模型):

  1. let (﹤﹤) (a:_ Actor) msg = a.Post msg  
  2.  
  3. let ping =  
  4.     let count = ref 5  
  5.     { new PongMsg Actor() with  
  6.         override self.Receive(message) =  
  7.             match message with  
  8.             | Pong(pong) ->  
  9.                 printfn "Ping received pong" 
  10.                 count := !count - 1  
  11.                 if (!count > 0) then  
  12.                     pong ﹤﹤ Ping(self)  
  13.                 else 
  14.                     pong ﹤﹤ Finished  
  15.                     self.Exit() }  
  16.  
  17. let pong =   
  18.     { new PingMsg Actor() with  
  19.         override self.Receive(message) =  
  20.             match message with  
  21.             | Ping(ping) ->  
  22.                 printfn "Pong received ping" 
  23.                 ping ﹤﹤ Pong(self)  
  24.             | Finished ->  
  25.                 printf "Fininshed" 
  26.                 self.Exit() }  

例如在pong對(duì)象的實(shí)現(xiàn)中,我們使用模式匹配,減少了不必要的類型轉(zhuǎn)換和賦值,讓代碼變得簡潔易讀。還有一點(diǎn)值得順帶一提,我們?cè)贔#中可以靈活的定義一個(gè)操作符的作用,在這里我們便把“﹤﹤”定義為“發(fā)送”操作,避免Post方法的顯式調(diào)用。這種做法往往可以簡化代碼,從語義上增強(qiáng)了代碼的可讀性。例如,我們可以這樣啟動(dòng)乒乓測(cè)試:

ping ﹤﹤ Pong(pong)至于結(jié)果則與C#的例子一模一樣,就不再重復(fù)了。

F#中的弱類型消息

可是,F(xiàn)#的世界就真的如此美好嗎?試想,我們?cè)撊绾螌?shí)現(xiàn)一個(gè)需要接受多種不同消息的Actor對(duì)象呢?我們只能這樣做:

  1. let another =   
  2.     { new obj Actor() with  
  3.         override self.Receive(message) =  
  4.             match message with  
  5.               
  6.             | :? PingMsg as pingMsg ->  
  7.                 // sub matching  
  8.                 match pingMsg with  
  9.                 | Ping(pong) -> null |> ignore  
  10.                 | Finished -> null |> ignore  
  11.                   
  12.             | :? PongMsg as pongMsg ->  
  13.                 // sub matching  
  14.                 match pongMsg with  
  15.                 | Pong(ping) -> null |> ignore  
  16.                   
  17.             | :? (string * intas m ->  
  18.                 // sub binding  
  19.                 let (s, i) = m  
  20.                 null |> ignore  
  21.                   
  22.             | _ -> failwith "Unrecognized message" } 

由于我們必須使用object作為Actor接受到的消息類型,因此我們?cè)趯?duì)它作模式匹配時(shí),只能進(jìn)行參數(shù)判斷。如果您要更進(jìn)一步地“挖掘”其中的數(shù)據(jù),則很可能需要進(jìn)行再一次的模式匹配(如PingMsg或PongMsg)或賦值(如string * int元組)。一旦出現(xiàn)這種情況,在我看來也變得不是那么理想了,我們既沒有節(jié)省代碼,也沒有讓代碼變得更為易讀。與C#相比,唯一的優(yōu)勢(shì)可能就是F#中相對(duì)靈活的類型系統(tǒng)吧。

C# Actor不好用,F(xiàn)#也不行……那么我們又該怎么辦?

【編輯推薦】

  1. 看Erlang中Actor模型的執(zhí)行方式和優(yōu)劣
  2. Erlang面向分布與并發(fā)的編程語言
  3. Erlang十分鐘快速入門
  4. 因并發(fā)而生 因云計(jì)算而熱:Erlang專家訪談實(shí)錄
  5. 淺析Erlang分布的核心技術(shù)
責(zé)任編輯:yangsai 來源: 老趙點(diǎn)滴
相關(guān)推薦

2009-08-20 18:13:03

F#和C#

2010-04-07 16:51:59

F#

2010-01-07 10:04:18

F#函數(shù)式編程

2010-01-26 08:25:06

F#語法F#教程

2012-03-12 12:34:02

JavaF#

2009-05-01 11:17:41

ADNF5思科

2009-09-10 14:18:59

Functional F#

2022-11-10 08:26:54

.NET 7C# 11

2009-11-16 09:05:46

CodeTimer

2010-03-26 19:22:08

F#代理

2010-04-06 15:20:56

ASP.NET MVC

2010-05-13 09:21:44

F#Visual Stud

2010-01-15 08:33:13

F#F#類型推斷F#教程

2009-08-05 16:04:27

C# Actor模型

2009-11-09 17:51:51

F#函數(shù)式編程

2009-08-19 09:42:34

F#并行排序算法

2009-08-13 17:39:48

F#數(shù)據(jù)類型Discriminat

2011-06-09 09:52:41

F#

2010-03-26 19:03:19

F#異步并行模式

2010-03-26 18:31:59

F#異步并行模式
點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)