本文章是由機器翻譯。

Polyglot 的程式設計人員

混合和對應的語言

Ted Neward

內容

polyglot 的程式設計
在練習 polyglot
選擇之後,成本

在 Web] 和 [用戶端與伺服器程式設計中之前的時間, 是很常見的是單一平台或系統的上方以單一語言撰寫整個應用程式。 考慮,例如,在非常普遍的 FoxPro 平台上年的應用程式程式設計的一個裝訂位置。 它會提供一個使用者介面的格式語言和程式庫、 資料存取和儲存的格式以及的常式集合提供傳統的程式庫支援例如數學和帳戶處理。 語言和平台,包裝至單一的環境 FoxPro 是程式設計人員無法瞭解一個語言 / 平台和保留成功的如何在傳統範例和更重要的是,gainfully 採用。

然後隨附特製化,新的語言和每個某特定和特定用途的工具。 關聯式資料庫的超額,且 SQL 成為,用來存取它們及修改它們的語言。 GUI 用戶端開發一開始移動程序的語言,例如 C 和依照 Pascal 命名法,然後,隨著物件方向的是 C ++ 和 Delphi。 語言 (例如 Perl 和 Python 的管理主控台為基礎作業系統擴充功能,在命令殼層,例如在 bash 和 Korn 殼層中找到的最常以 UNIX 為基礎系統,成為功能強大的工具。 然後隨附 Web,並與 HTML、 CSS 和 JavaScript 成為語言的使用者介面顯示的選擇。

程式設計語言的世界有永遠不會看到一點勿干擾時間,但最近年大量的語言有 exploded 到程式設計的場景包括程序的語言 (例如 [注音標示],Windows PowerShell 和 Python 和 F #] 和 [即將等功能的語言]。 現有的語言會取得新的功能,例如,查詢功能的資料 Microsoft 已加入 C# 使用 LINQ,並現在的程式設計語言的開發一個新的階段已開始在其中為網域特定語言 (DSL) 的新自訂語言正在開發特定工作的應用程式。 如需 DSL 的範例,請參閱從 Visual Studio 2008 所服務的站台行,啟動 MSDN Magazine (msdn.microsoft.com/magazine/cc164250) 的版本 (英文)。

到處您開啟,設定移除新的語言。 當然,多國語言的程式設計人員會為它,predate 」 polyglot 程式 」 產生的 Neal Ford 2006 年 12 月的部落格張貼 (memeagora.blogspot.com/2006/12/polyglot-programming.html) 一詞時, 標題 (不當然) 」 程式設計 Polyglot 」。

polyglot 的程式設計

[確定],讓我們已建立有很多的語言有,和有是可能的一種語言特別適用於每個問題,您需要位址。 經驗的.NET 程式開發人員必須瞭解它們如何一起配合。 這會是這個資料行的焦點。

今天考慮其中一個最常見的問題。 開發人員被要求來縮放其程式 — 其網站和服務特定 — 為更大或更新版本的數字,比以前的使用者。 客戶所提供存取給他們自己的帳戶資訊直接。 這表示,其中的應用程式可能必須調整,以百的使用者數 (假設,增加數量的呼叫期間在成長的中心員工使用者 spurt),現在相同的應用程式必須會擴充至可能千分位 (如果不是百萬名使用者。

使用網站的開發人員必須管理達到良好的效能和執行緒安全。 只鎖定每個方法,以序列化整個系統透過存取不是理想的解決方案因為它不會縮放。 設定,縮放的應用程式時,特別是,便會正確管理並行存取,沒有簡單的戰技 ; 它保留甚至資深開發人員設定晚期晚上。 即使發行的 PEX (Research.Microsoft.com/projects/pex) 和 Chess (Research.Microsoft.com/projects/Chess),執行靜態程式碼分析,並將執行單元測試的排列方式的新工具發現多執行緒處理的錯誤,您仍然需要自行,控制並行存取,相當低層級使用 C# 「 鎖定 」 陳述式或不同的並行存取控制項類別從 System.threading。

您所面臨的挑戰到嗎? 如何是好是您的並行處理技巧? 快速,什麼 System.Threading.Monitor、 System.Threading.Mutex 和 System.Threading.Semaphore 差異?

這裡您應該會開始查看特定的語言選擇的值。 可能是這種複雜的程式碼很容易撰寫和維護,如果您選擇功能的語言,例如 F #,進行的不變性及零的副作用的一般 tendencies 會防止需要明確的並行存取控制項,] 或 [網域特定語言撰寫的是設計來隱藏從開發人員使用並行存取詳細資料的 Ruby。

這個概念,更具體,想像一下 Web 應用程式需要執行一些傳統上同步的作業,以便例如執行某些檔案為基礎 I / O (或發出資料庫或 Web 服務呼叫)。 通常,要從 C# 程式碼所做的最簡單件事是開啟檔案透過在傳統使用陳述式,讀取的內容儲存至位元組陣列,然後關閉檔案與此類似的方式:

byte[] pixels = null
BinaryReader br = 
         new BinaryReader(new FileStream(filename, FileMode.Open)); pixels = br.ReadBytes(4096);

它可能簡單,但它也 horribly 的序列化]。 檔案 I / O 作業正在進行時,沒有其他的處理可能會發生在這個執行緒上。 (一個簡單的檔案 I / O 作業,這是可能不是一個主要的考量至少不之前,站台嘗試向上擴充可能發生這些作業取得真正大型的數目)。 會是比較好讀取該的檔案,使用可透過 CLR 執行緒集區,非同步作業,如 [圖 1

[圖 1 讀取檔案,以非同步方式

delegate byte[] AsyncOpenMethod(string filename);
static byte[] AsyncOpen(string filename)
{
    byte[] pixels = null;
    using (BinaryReader br =
        new BinaryReader(new FileStream(filename, FileMode.Open)))
    {
        Pixels = br.ReadBytes(4096);
    }
}
static void AsyncOpenTheFile(string filename)
{
    byte[] pixels = null;
    AsyncOpenMethod aom = new AsyncOpenMethod(Class1.AsyncOpen);
    IAsyncResult iar = aom.BeginInvoke(filename, null, null);
    while (iar.IsCompleted == false)
    {
        // Do something?
    }
    pixels = aom.EndInvoke(iar);
}

但是,程式碼的簡單之前是只在開發人員的母親無法愛,和清楚地處理大部分的目前程式碼,多個只讀取檔案。 現在,請檢查所撰寫類似的常式 F # 外觀:

async {
use inStream = File.OpenRead(filename)
let! pixels = inStream.AsyncRead(4096)
}

我不想太深探討如何會 F # 程式碼執行此,但,讓 ! 運算式,會告訴 F # 編譯器產生的運算式為非同步的運算式,AsyncRead 方法 F #,以無訊息模式 tacks 在標準的 System.IO.Stream 的衍生類別,透過呼叫延伸方法的 F # 語言功能的一個。 它實際上讀取檔案,以非同步方式,並傾印結果位元組陣列的運算式的左邊,所有不需任何進一步的程式碼從開發人員所需後。

讓我們將這項實際的內容放入資料。 做為的夜間的維護循環的一部分,Web 服務必須讓更容易離線儲存的檔案數份備份。 在的 F # 程式碼,所有完成非同步執行此檔案的複本,看起來像 [圖 2 中。

[圖 2] 的 [備份複製

#light

open System.IO

let CopyFileAsync filename =
    async {
        use inStream = File.OpenRead(filename)
        let! pixels = inStream.AsyncRead(4096)
        use outStream = File.OpenWrite(filename + ".back")
        do! outStream.AsyncWrite(pixels)
    }

let tasks = [ for i in 1 .. 10 -> CopyFileAsync("data" + i.ToString()) ]
let taskResults = Async.Run (Async.Parallel tasks)

同時,讀取和寫入會執行非同步方式,而 10 個工作 (一個用於每個資料檔案) 的整個集合也會以非同步方式處理。 當您考慮這會需要在 C# 中時,會您而撰寫? (機會 Coble 進入 F #] 和 [非同步程式設計的大幅更多詳細 」 簡單的非同步: 建置同時的應用程式,從簡單的 F # 運算式「 在 10 月) 2008 發行的 MSDN Magazine .

在練習 polyglot

實際上,F # 和 C# (或任何其他 CLR 語言) 之間的互通是相當簡單一旦也瞭解這兩種語言中的 (哪些語言會變成在 [IL 層級),程式碼 「 形狀 」。 在介面上,這似乎很簡單 — 之後所有,如何的不同方法呼叫可以兩個的語言之間的 Common Language Specification (CLS) 規定,否則拙劣問題,例如參數的位置、 基本型別,以及位元組順序許多?

讓我們將的測試。 採取某些簡單的 F # 程式碼開始,讓我們來編譯它和開啟成 DLL 並使用 ILDasm (中繼語言反組譯工具或反映程式,您習慣使用任何工具) 來檢查哪些它看起來。 您可以再 Graduate 更複雜 F # 運算式例如,非同步工作流程程式碼的機會 Coble 他 10 月 2008 文件,我稍早所述的呈現。

為開始與採取一些簡單的 F # 程式碼

let x = 2

如果我們假設它是存在於呼叫 Module1.Fs,IL 中預設的"F # Library"專案檔 [圖 3] (幾件事情的註解將簡短的 IL 清單) 會產生它。

[圖 3 IL 背後讓 x = 2

.assembly Library1
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
  FSharpInterfaceDataVersionAttribute::.ctor(int32, int32,
  int32) = ( 01 00 01 00 00 00 09 00 00 00 06 00 00 00 00 00 ) 

  // ...
}

.class public abstract auto ansi sealed beforefieldinit Module1
       extends [mscorlib]System.Object
{
  .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
  CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.
  FSharp.Core.SourceConstructFlags) = ( 01 00 07 00 00 00 00 00 ) 

  .method public static int32  get_x() cil managed
  {
    // Code size       6 (0x6)
    .maxstack  4
    IL_0000:  ldsfld     int32 '<StartupCode$Library1>'.$Module1::x@3
    IL_0005:  ret
  } // end of method Module1::get_x

  .method private specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // Code size       13 (0xd)
    .maxstack  3
    IL_0000:  ldc.i4.0
    IL_0001:  stsfld     native int '<StartupCode$Library1>'.$Module1::_init
    IL_0006:  ldsfld     native int '<StartupCode$Library1>'.$Module1::_init
    IL_000b:  pop
    IL_000c:  ret
  } // end of method Module1::.cctor

  .property int32 x()
  {
    .custom instance void [FSharp.Core]Microsoft.FSharp.Core.
    CompilationMappingAttribute::.ctor(valuetype [FSharp.Core]Microsoft.
    FSharp.Core.SourceConstructFlags) = ( 01 00 09 00 00 00 00 00 ) 
    .get int32 Module1::get_x()
  } // end of property Module1::x
} // end of class Module1

.class private abstract auto ansi sealed beforefieldinit 
    '<StartupCode$Library1>'.$Module1
       extends [mscorlib]System.Object
{
  .field static assembly native int _init
  .custom instance void [mscorlib]System.Runtime.CompilerServices.
   CompilerGeneratedAttribute::.ctor() = ( 01 00 00 00 ) 
  .field static assembly initonly int32 x@3
  .method private specialname rtspecialname static 
          void  .cctor() cil managed
  {
    // Code size       8 (0x8)
    .maxstack  3
    IL_0000:  nop
    IL_0001:  ldc.i4.2
    IL_0002:  stsfld     int32 '<StartupCode$Library1>'.$Module1::x@3
    IL_0007:  ret
  } // end of method $Module1::.cctor

} // end of class '<StartupCode$Library1>'.$Module1

如果您追蹤透過 IL 程式碼,您會注意到兩件事,; 先,Module1 和未繫結資料的常數為其初始值為 2,但組件會載入透過在型別 (.cctor) 編譯器產生 StartupCode $Library1 類別建構函式初始化的第二在名稱 x 從 F # 程式碼所繫結在 CLS 層級為類別的靜態屬性呼叫。 也就是說而您可能會想要想 x 的常值編譯器可以內嵌,編譯器會選擇,它呈現為靜態屬性。

這表示,然後存取這個繫結,x,將需要的 C# 下列如下的程式碼:

namespace ConsoleApplication1
{
    class Program
    {
        static void Main(string[] args)
        {
            Console.WriteLine("F#'s x = {0}", Module1.x);
        }
    }
}

因此遠,所以好。 但 x 是相當簡單繫結,並因此,您會預期要簡單的存取問題。 稍微更複雜的內容存在一些技巧的問題,因此讓我們進行更複雜,以確定 F # 的對應至 CLS 仍在自動會有意義。

F # 程式碼

let add a b = a + b

編譯器產生額外的 IL,至已編譯的 F # DLL 中的 「 Module1 類別:

.method public static int32  'add'(int32 a,
                                   int32 b) cil managed
{
  // Code size       5 (0x5)
  .maxstack  4
  IL_0000:  nop
  IL_0001:  ldarg.0
  IL_0002:  ldarg.1
  IL_0003:  add
  IL_0004:  ret
} // end of method Module1::'add'

這是幾乎精確地哪些函式的 C# 版本會碼,所以需要以下說明的許多不。 呼叫它也是微不足道。

但 F # 的優點之一,它會將函式頂級的值,但這會變得更明顯的 (且更難的) 當您啟動建置函式,取得函式如下所示的引數對等的內建 F # 程式庫對應函式,當做引數一起函式套用至每個項目,在的清單的清單,並傳回新的清單,包含結果:

let mymap (l : 'a list) (f : 'a -> 'b) = List.map f l

這是處理清單的特別是 「 功能 」 的方法: 而逐一查看該項目-的項目,語言功能會做為參數的函式然後套用到清單,產生其中一個單一結果 (稱為 「 摺疊 」 作業)] 或 [包含結果的每個新的清單中的每個元素 (我們 「 對應 」)。

請注意, [圖 4 在中如何這變成複雜一點 IL 在編譯 (Compilation) 之後。 再次,它對應到公用靜態方法 Module1 類別上驚人,什麼會讓這難以從 C# 互動,無法但是,靜態方法採用 F # 清單 」 必須 (表示,Microsoft.FSharp.collections.list 參數化型別執行個體),為輸入和傳回型別,連同函式 (表示) 的 Microsoft.FSharp.core.FastFunc dually-型別的參數化執行個體的執行個體與第二個輸入。

[圖 4: IL,為對應的函式

  .method public static class [FSharp.Core]Microsoft.FSharp.Collections.
       List'1<!!B> 
          mymap<A,B>(class [FSharp.Core]Microsoft.FSharp.Collections.
              List'1<!!A> l, class [FSharp.Core]Microsoft.FSharp.Core.
                         FastFunc'2<!!A,!!B> f)
          cil managed
  {
    // Code size       11 (0xb)
    .maxstack  4
    IL_0000:  nop
    IL_0001:  ldarg.1
    IL_0002:  ldarg.0
    IL_0003:  tail.
    IL_0005:  call       class [FSharp.Core]Microsoft.FSharp.Collections.
       List'1<!!1>
       [FSharp.Core]Microsoft.FSharp.Collections. 
       ListModule::map<!!0,!!1>(class [FSharp.Core]Microsoft.
       FSharp.Core.FastFunc'2<!!0,!!1>,
       class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!!0>)
    IL_000a:  ret
  } // end of method Module1::mymap

這要需要一些嚴重的 C# 程式碼正常運作。 這個範例中,我會希望取得整數的集合,並將它們至其對應的字串表單中轉換的一些 C# 程式碼。 (事實上我可以這麼夠輕易從 C# 中是這裡無關 — 我需要知道如何執行之前,我可以處理在複雜的簡單動作) 從 C# 表示要必須成功發生幾件事中呼叫這個: 輸入的集合之使用權必須轉換成的 F # 清單型別,必須將轉換套用至每個項目的函式的 F # FastFunc 」 到執行個體和傳回的 F # 清單需要轉換成 C# 可以使用或者直接使用其原生的 F # 形式的型別。

C# 程式碼將需要這些 F #,型別,並且因此第一個步驟即可,將適當 F # 組件參考加入,在這種情況下 FSharp.core.dll。 建構的 F # 清單,但是,就像建構 C# 清單一樣,不 — 而非透過建構函式在 C# 集合中傳遞,F # 假設清單設定使用建置缺點 」 運算子是在 F # 清單 <> 類別上的靜態方法。 也就是說,F # 的程式碼

let l1 = [1; 2; 3;]

變成而未加工而醜怪的 IL 中顯示 [圖 5 .

[圖 5] 讓 l1 = [1; 2; 3;]

  IL_0000:  ldc.i4.1
  IL_0001:  ldc.i4.2
  IL_0002:  ldc.i4.3
  IL_0003:  call       class [FSharp.Core]Microsoft.FSharp.Collections.
List'1<!0> class [FSharp.Core]Microsoft.FSharp.Collections.
List'1<int32>::get_uniq_Empty()
  IL_0008:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)
  IL_000d:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)
  IL_0012:  newobj     instance void class [FSharp.Core]Microsoft.FSharp.
Collections.List'1/_Cons<int32>::.ctor(!0,
              class [FSharp.Core]Microsoft.FSharp.Collections.List'1<!0>)

若要將 C# 陣列轉換成適合 mymap 方法傳入的 F # 清單,我必須重複呼叫,在清單,傳入新的標頭使用者每次,靜態的缺點方法,請注意,因為該項目加入到標頭的清單,保留 F # 清單以 C# 陣列,在相同的順序這表示我必須從陣列的結尾開始,並向後運作:

int[] scores = {1, 2, 3, 4, 5};

var fs_scores = Microsoft.FSharp.Collections.List<int>.get_uniq_Empty();
for (int i = scores.Length-1; i >= 0; i--)
{
  fs_scores = Microsoft.FSharp.Collections.List<int>.Cons(scores[i],
                                                          fs_scores);                
}

已經這已成為在痛苦的項目。 取得 F # 的 FastFunc 型別的執行個體是更冗長,因為 FastFunc 型別,核心 system.delegate 類型,一樣不是真的要在程式設計人員,執行個體化,但是會而處理 F # 編譯器。 一般來說,建構的這個執行個體,在 F # 程式碼,編譯器實際上會產生繼承自 FastFunc 的內部類別,從 C#,但是無法做有會需要更多的工作。

突然這個 F # 函式呼叫似乎太多的好處工作所衍生的方式。

哪些所有的服務,說明是對應至其基礎平台語言的重要決定要在哪裡,以及如何以 polyglot 的方式使用語言。 一般執行內部的 F # 的例如,可以開啟出要直接從 C# 起來一點也不難。 這不表示放棄 polyglotism 的概念,這就表示您必須小心選擇您使用一起以及哪些用途的語言。 從多正的觀點來看考慮,其中一個機會的 10 月文件,在底端的其他程式碼範例 (請參閱 [圖 6 )

圖 6: 原始程式碼 Plus 修改

open System
open System.IO
open Microsoft.FSharp.Control.CommonExtensions

#nowarn "057"
let aCountSpace filename size = 
  async {
     let space = Convert.ToByte ' '
     use stream = File.OpenRead (filename)
     let bytes = Array.create size space
     let! nbytes = stream.ReadAsync (bytes,0,size)
     let count = 
       bytes
       |> Array.fold_left (fun acc x -> if (x=space) then acc + 1 else acc) 0
     return count
  }

let aCounted (files : FileInfo array) = 
    files
    |> Array.map (fun f -> aCountSpace (f.FullName) (int f.Length))
    |> Async.Parallel 
    |> Async.Run

請注意我已修改它稍微。 不過,只有我在這裡所做的修改,是 FileInfo 的在 aCounted 」 方法中為項目在由機會的原始程式碼中 F # 編譯器推斷的陣列,指定 [檔案] 引數。 aCounted 」 的 IL 簽名碼現在看起來像

 .method public static int32[] aCounted(class [mscorlib]System.IO.FileInfo[] files) cil managed

這一來很所示,在從 C# 呼叫:

int[] fileCounts = 
  Module1.aCounted(new DirectoryInfo(@"C:\Projects\Test").GetFiles("*.txt"));
foreach (var ct in fileCounts)
{
    Console.WriteLine("Count = {0}", ct);
}

還一樣簡單,因為它是以呼叫,這個新版本會保留在 10 月的機會討論到的完整的非同步執行功能。

請注意這裡的 C# 使用完全任意完全根據事實上我習慣在 C# 比在 Visual Basic 或 C ++/ CLI 中,有說過,它不應該很難查看相同的程式碼會在其中一個這些語言。

選擇之後,成本

一定還有一個成本當然取得一個 polyglot 的方法。 第一個主要的麻煩是很明顯,因為我們已經已執行至查看 — 想要讓開發人員使用的專案上不同的語言必須瞭解如何將這些語言對應到基礎平台 —,接著彼此。 這表示只依賴編譯器正確項目將不再運作。

開發人員需要請參閱編譯器如何將語言建構對應至基礎平台 (在本例,CLR),及其他語言如何會挑選這些建構。 好消息會衍生自事實上語言不會變更許多一段時間,甚至的看過一些很 Radical 移位,透過 [最後] 十年的 C# 或 Visual Basic,語言因此一旦您已學會,語言的對應的一些這項資訊保持相當不變。

第二個問題都衍生自第一個 — polyglot 程式的偵錯可以是比偵錯的其中一個 monoglot 更難的只為 owing 事實上,現在開發人員必須說話兩個或多個) 的語言,而不是只。 在我所提供的程式碼的情況下例如,偵錯將會需要在 C# 程式碼中設定中斷點和逐步執行透過,可能到 F # 程式碼和重新顯示後。 許多方面,這是新的執行任何動作-F # 是一個更多的語言上, 至您想要知道的語言清單。

在某些開發商店,使用 C# 程式開發人員會只信任 (不論所宣告或透過信仰單元測試中),F # 程式碼如通告的程式碼,並逐步透過而非步驟 F # 程式碼,就像許多 Visual Basic 和 C# 的開發人員在透過呼叫的程式碼逐步 Unmanaged C ++ 程式庫時所執行中。

在未來資料行,我將探討 polyglot 的方法讓程式設計更容易,和在其他語言和其優勢,polyglot 程式設計人員的其他方法。

您提出問題或意見,請以 Ted 寄 polyglot@Microsoft.com.

Ted Neward 會是 ThoughtWorks 的國際性的顧問,專精於可靠、 靈活的企業系統的一個主要顧問。 他具有寫入許多的書籍是 Microsoft MVP 架構設計人員、 INETA 演說和 PluralSight 講師。 在 Ted 的連接 ted@tedneward.com或讀取在他的部落格 blogs.tedneward.com.