蒯因与引用透明

蒯因又称奎因,美国哲学家,逻辑学家,逻辑实用主义的代表,与罗素齐名,强调系统的、结构式的哲学分析,主张把一般哲学问题置于一个系统的语言框架内进行研究。

蒯因从逻辑的观点出发,把语言分析作为哲学研究的核心内容,这就为哲学的语言转向划上了一个圆满的句号:自蒯因哲学起,当哲学家开始讨论哲学问题的时候,都需要首先考察所要讨论的命题的意义,而不会像传统哲学家那样依然专注于对世界存在的思考。

蒯因强调以严格精确的形式语言表达我们的思想,蒯因这一逻辑应用在我们编程语言中,演化成了各种面向对象或面向函数编程的原则。

下面我们谈谈函数副作用的问题,也称边界问题,降低甚至消灭副作用已经成为我们编程的一个重要原则,不要让一个方法或函数执行主要功能时,会产生其他意想不到的次要功能,这些额外功能不是我们要求的,称为副作用。

更严格地消灭副作用方式是引用透明(referentially transparent),这是面向函数范式FP中的一个术语,也是FP语言如Scala等相比Java特色所在。

为什么他们称为引用透明Referentially transparent一文中谈到:

“引用透明”(指称透明)这个概念来自于蒯因(Quine)的关于自然语言哲学。看看下面语句:BanQ比老鼠大, 如果把"BanQ"去除,留个空白在那里,显然变成:“??? 比老鼠大”。哲学家认为这是一个Context场景,通过填补场景中的空白,你得到了这个语句真实含义。

再次想一下北京这个城市,它是一个thing,有许多方式可以指称或引用(refer)它,“中国首都”或“在经纬XXX度的城市”。

因此,“引用透明”(referentially transparent)中的“引用referentially”这个词语的含义是在谈论这样一个事实:北京这个事物(thing)有很多名称,有很多途径来表达指称这个事物。

“引用透明”(referentially transparent)中“透明transparent”含义是“没有什么不同”,“都是一样”。

蒯因认为: 如果我们能够用不同的名称替代上面语句中的空白,但是没有改变任何意思,总是得到同样的结果,那么这个场景Context就是“引用透明”的。

如:
[北京]比老鼠大 是真;
[中国首都]比老鼠大 是真
[在经纬XXX度的城市]比老鼠大 是真

这些命题的真正含义不会因为我们选择不同的引用或指称名称而受到影响。我们就可以认为场景"??? 比老鼠大"是引用透明的。

与引用透明相反,蒯因认为存在引用不透明( REFERENTIALLY OPAQUE),名称的改变对于整个语句意思非常重要。

比如:??? 有9个字符。那么:[北京]有9个字符 是伪的。

这样场景“??? 有9个字符” 的含义因为名称指称(name/refernce)不同而不同。

罗素 蒯因等代表的形式逻辑是数学或编程语言的元语言,在数学中,所有函数都是引用透明的,比如sin(x),无论如何调用,结果只依赖x值,或者说:对于一个特定的x值,无论怎么调用这个函数得到都是同一个结果。

因为国内很多人只知道数学,而不知道数学后面的哲学与形式逻辑,就把我们编程语言中的这些约束和数学划上关系,实际上真正有联系的是数学背后的形式逻辑,或称符号逻辑。

对于编程语言,引用透明的含义可以表达为:函数的输出只依赖于其输入,引用透明就没有副作用,也无需指定前后运行顺序,比如:


int getResult(x){
return x;
}

是一个引用透明无副作用的方法函数,而:

int getResult(x){
return x + g;
}

则是引用不透明,因为getResult结果不但依赖x,还依赖g值。

那么g值是什么呢?一般是这个方法所在类的属性字段,如完整是如下:


public Class A{
int g;

int getResult(x){
return x + g;
}
}

很显然,含有属性状态g的类A,我们称为有状态类,也就是说:有状态的类都不是引用透明,都是引用不透明的。

引用透明概念由于本源起源于形式逻辑,而数学也建立在形式逻辑基础上,很显然,关系代数中的幂等函数含义与引用透明概念是一致的。

引用透明或幂等函数都是多次调用都会得到同样的结果,而且这个结果只依赖输入,不依赖其他值,这就为我们进行并行或异步计算提供了可能,也会分布式架构的粒度切分提供了可能,我们如果把一个引用不透明的有状态服务切分为引用透明无状态服务,那么无疑性能和可扩展性Scalable大大提高了。

引用透明虽然是编程范式中一个原则,实际也是性能设计可扩展性中的一个原则,这就完美体现了良好的性能和良好的编程习惯是密切不可分的。


[该贴被banq于2011-08-29 15:14修改过]
[该贴被banq于2011-08-29 15:15修改过]
[该贴被admin于2011-08-29 15:18修改过]

其实在OO也有,如单一职责原则。

论物有态,论事无态。事如同无态服务、函数,只要是同一个输入就是同一个结果。

这里我提一下,我认为值对象不可变,也是认识了函数意义后,所得到的见解。

对于f(x),x为值对象,若x可变,则x为有态的。当无态的f(x)引入有态的x后,那么整个f(x)就失去函数、谓词、无态服务等意义。

对于banq所说的有态:


public Class A{ int g; int getResult(x){ return x + g; }}

我并不这么认为,我认为:是可变的才会有态。若变量g是在实例a中是不可改变的,那么实例a也是无态的。对于在实例a的边界中的函数getResult(x),因g不变,则g就相当于一个常量而已,那么getResult(x)应表示如下:


getResult(x){ return x + C;//C是任意常量 }

这时函数也是无态的。


为什么基本类型值在任何情况下不会产生有态问题,是因为其本身就是不可变。

2011年08月29日 17:35 "@SpeedVan"的内容
则g就相当于一个常量而已 ...

如果你想让g成为一个常量,或者具有不可变性,那么就要显式的声明它,也就是奎因倡导的严格精确的形式语言,那么A类中的g就要加final:


public Class A{
final int g;

int getResult(x){
return x + g;
}
}

Java的问题就是这样相对随意性,没有Scala等函数语言更加精确,说不好听比较较真。

所以,搞多了科学等精确性的人的思维也比较容易纠结,中国人文化熏陶后不太愿意过于理性和精确,反映到编程上就容易模棱两可,而Java提供了这种模糊机会,而Scala则没有,结果Scala对于不愿意纠结的人来说比较难学。

2011年08月30日 08:56 "@banq"的内容
final int g; ...

final若果是声明基本类型变量(不可变性),则可以达到banq你所提到的“严格精确的形式语言”。但java的final是不严谨的,对于对象他是表示引用不变,也就是说若果g指向一个对象,同时该对象可以允许修改,那么就失去了final的初衷了。

我们可以试想String实例具有修改自身值的方法:new String("123").setValue("345")。这样的String还是String么?一个字符串对象既是123又是345。这使我们讨论值时已失去值的意义,所以我在DDD一书中看到的是不可变的值对象,Eric Evans也提到值对象使我们可以使用函数式的特性。同一个输入,同一个输出,前提如何保证同一个输入。

2011年08月30日 14:46 "@SpeedVan"的内容
值对象使我们可以使用函数式的特性 ...

值对象的特性是不可变性,不可变性和引用透明是技术架构中两个根本原则:要么不可变(静止数据);要变是可反复的(计算结果是动态数据,可缓存)。软件细分到这种程度就应该达到最优化的可扩展性了Scalable。

引用透明的要旨是:对于一个特定的输入值,经过反复调用都会得到同一结果

注意要旨中有两个关键点“反复”和“结果”:
1.“反复”就能够实现failover失败恢复,保证可靠性,这个计算函数如上面getResult如果计算时当机或者出现硬件或软件底层错误,那么由于可“反复”,那么可再次调用运行计算。由于可“反复”,可以多线程并行或多台服务器多次同时或异步运行计算函数getResult,这为大规模分布式计算提供了最基础的依据。

2.“结果”意味着这个结果值是稳定的,那么我们就可以这个特定的输入值为Key,“结果”为值,存储到内存缓存中比如memcached等,很多分布式数据库NoSQL也都需要一个Key值,因此如何抓到这个Key就成为分布式状态设计的关键,实体模型因为DDD建模肯定有唯一标识作为Key,那么复杂计算或复杂调用查询过程中动态数据key可依据“引用透明”找到key。包括Map/Reduce算法实现Hadoop和HBase的结合也是依据这个原则。


总结:数据状态需要不变性Immutable;功能行为需要referentially transparent,这两个设计得到保障,系统的架构也就达到目标了。
相关文章:
NOSQL存储的基于事件的事务实现
基于事件和Im-memory的LMAX架构
如何打败CAP定理?
[该贴被banq于2011-10-19 19:53修改过]