你的程式語言可推導(Reasonable)嗎?

banq發表於2015-01-27
在函式程式設計世界中,我們經常說“reason about可推導”,或者說,我們要讓我們的程式可推導。那麼這個可推導是什麼意思呢?

Is your programming language unreasonable? | F# fo一文以比較通俗方式解釋了reason。

很多人說C已經具有了F的大多數函式功能,是否沒有必要轉到F#?同樣問題也存在Java和Scala之間。其實這些新舊語言的本質區別就是是否可推導。

reasoning意思來自於數學和邏輯,但是可以使用簡單實用的方式定義如下:

"reasoning about the code(程式碼是否可推導 是否合理 是否經得起推敲質疑)" 的意思是:你是否可以根據當前眼前的資訊就能得出結論,下判斷?而不是需要進入程式碼內部研究一番才敢判斷。

(banq注:在日常生活中,喜歡下結論的方式其實是草率的思維方式,因為日常我們所見到所聽到資訊都不是可推導的,如果你在純數學理論世界習慣了,會造成濫用“下結論”哦)。

從另外一個方面說,你只要看到一部分程式碼的行為,你就能有預期,你只需要理解介面,而不必理解介面背後具體實現做什麼,你就能預期判斷了。

有很多方式指導如何達到這種目標:如取名指南,格式規則,設計模式等等。

下面以一段程式碼說明如何使得程式碼變得更加可推導,可預期:

var x = 2;
DoSomething(x);

// What value is y? 
var y = x - 1;
<p class="indent">

上面是一段Javascript程式碼,你能判斷最後y值是多少嗎?答案是-1,為什麼?下面是整個事情真相:

function DoSomething (foo) { x = false}

var x = 2;
DoSomething(x);
var y = x - 1;
<p class="indent">

是的,如此可怕,Dosomething直接訪問x而不是透過引數,這樣它將其轉為一個boolean,那麼從x減1就會將x實現cast,從false變為0;那麼y結果是-1。

你是否很討厭這樣,很抱歉誤導了你,這裡只是想說明當語言不可預期的時候變得多麼討厭。

Javascript是非常有用重要的語言,但是沒有人宣稱可推導是它的強項,實際上,大多數動態語言都是這樣難以可推導

而C#要比Javascript更加更具有預期性,這是靜態語言加分之處。但是還是有問題,待續。

現在我們有了讓語言變得可預期的第一個法則:
1.變數不應該允許改變它們的型別。

案例2. 我們要建立Customer類的兩個例項,那麼它們相等嗎?

// create two customers
var cust1 = new Customer(99, "J Smith");
var cust2 = new Customer(99, "J Smith");

// true or false?
cust1 == cust2;
<p class="indent">

答案是不可知,因為這個依賴於Customer是如何實現的,也就是Customer內部程式碼,因此這段程式碼不可預期。至少你得看到這個類是否實現IEquatable。

案例3:

// create a customer and an order
var cust = new Customer(99, "J Smith");
var order = new Order(99, "J Smith");

// true or false?
cust.Equals(order);
<p class="indent">

這也無法知道,因為這是兩個不同類的物件。(banq注:這種質疑有點苛刻,哪個程式設計師腦子有病去比較不同型別呢?而對於動態語言,苛刻的型別質疑會失去靈活性。原來喜歡質疑的人有時會鑽牛角尖的原因所在,不夠靈活)

案例4:

// create a customer
var cust = new Customer();

// what is the expected output?
Console.WriteLine(cust.Address.Country);
<p class="indent">

這也無法預期知道輸出什麼,因為不知道Address 這個屬性是否為空或不是,也就是說,當一個物件被初始化時,應該在構造器中將其內部初始化到一個有效狀態。當然如果語言幫助我們做到就更棒。

案例5:
1.建立一個customer.
2.加入到一個集合set,該集合使用hashing.
3.對customer做些事情
4.看看customer是否還在集合中?

// create a customer
var cust = new Customer(99, "J Smith");

// add it to a set
var processedCustomers = new HashSet<Customer>();
processedCustomers.Add(cust);

// process it
ProcessCustomer(cust);

// Does the set contain the customer? true or false?
processedCustomers.Contains(cust);
<p class="indent">

那麼集合中是否還包含Cumster這個物件呢?也許,也許不是。因為處理cust物件時有可能將其從集合中移除,如果我們假定無論物件和集合一旦建立就無法改變,不可變的,那麼我們就能得出肯定的結論。

案例6:
我們從倉儲CustomerRepository獲取一個customer:

// create a repository
var repo = new CustomerRepository();

// find a customer by id
var customer = repo.GetById(42);

// what is the expected output?
Console.WriteLine(customer.Id);
<p class="indent">

問題是:我們做完customer = repo.GetById(42)以後,customer.Id的值是多少?這也是無法推導的。

如果repo.GetById方法返回空或丟擲Exception,你能從customer中獲得Id嗎?

如果假設你的語言不允許null,並且不允許丟擲Exception,那麼你能得出什麼結論?那麼你會得到CustomerOrError類,其包含一個customer或一個錯誤。

在函式程式語言F,以上案例的約束都是內建的:
1.值不允許被改變型別(甚至包括暗地裡對int cast到float).
2.內部包含同樣資料的記錄預設是相等的。
3.比較不同型別的值會發生編譯錯誤。
4.值必須被初始化到一個有效狀態,不這樣做會編譯出錯。
5.一旦建立,值預設不可變。
6.空Null是不允許的。


相關文章