在函数编程世界中,我们经常说“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是不允许的。