你的编程语言可推导(Reasonable)吗?

在函数编程世界中,我们经常说“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;

上面是一段Javascript代码,你能判断最后y值是多少吗?答案是-1,为什么?下面是整个事情真相:


function DoSomething (foo) { x = false}

var x = 2;
DoSomething(x);
var y = x - 1;

是的,如此可怕,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;

答案是不可知,因为这个依赖于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);

这也无法知道,因为这是两个不同类的对象。(banq注:这种质疑有点苛刻,哪个程序员脑子有病去比较不同类型呢?而对于动态语言,苛刻的类型质疑会失去灵活性。原来喜欢质疑的人有时会钻牛角尖的原因所在,不够灵活)

案例4:


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

// what is the expected output?
Console.WriteLine(cust.Address.Country);

这也无法预期知道输出什么,因为不知道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);

那么集合中是否还包含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);

问题是:我们做完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是不允许的。


看到最后,意犹未尽的感觉有点强烈

2015-01-29 09:45 "@WinSpread"的内容
意犹未尽的感觉有点强烈 ...

呵呵,这是一个很大的话题,要让程序变得可推导,关键是对可变状态的围剿,纵观业界有两种方向:
1. 通过函数式语言让不变性成为语言的默认特性,这样可变状态变成特例。这种方式会让程序员尽量不要可变状态,就是有,也会努力花力气做好做完善。

2.让可变状态变成编程的核心,也就是说,从需求分析到代码设计,可变状态是我们的核心,重点对象,DDD实体方法属于这种范畴。

3.通过关注分离,将可变状态从系统中通过特殊框架和模式分离出来,比如Javascript世界的React.js,包括Actor模型倡导的reactive编程方式,所谓react即时反应,隐式其实隐藏着一个前提,在状态变化触发的即时反应,所以,这种方法也是一种以可变状态为核心的。javascript前端React.js相关参考:前端Flux架构简介,英文youtube视频:React.js Conf 2015 Keynote - Introducing React Native