人们通常认为动态类型更具表现力 ,而我在大多数情况下都愿意接受这种表征。 从本质上讲,具有动态类型的语言对我(程序员)的约束更少,并让我更自由地“表达自己”。 我现在不打算重新讨论静态类型的情况,这对于使用IDE编写或维护相当大的代码块的任何人来说都是很清楚的。 但是,我想指出一种特殊的意义,即动态类型比静态类型的表现力要差得多,其后果是。
(现在,请耐心等待一下,因为我将采取一条风景优美的路线来实现我的观点。)
简而言之, 动态类型比静态类型具有更少的表现力,因为它不能完全表达类型 。 当然,这是一个很愚蠢的重言式。 但这仍然值得一提。 为什么? 因为类型很重要。 即使使用Python,Smalltalk或Ruby之类的动态类型语言,类型仍然很重要。 要理解和维护代码, 我仍然需要了解事物的类型 。 确实,即使在弱类型JavaScript中也是如此!
这样的结果是,与静态类型的代码相比,动态类型的代码自记录的能力要低得多。 快速,我可以将以下功能传递给什么? 我从中得到什么?
function split(string, separators)
转换为锡兰后,此函数签名立即变得更容易理解:
{String*} split(String string, {Character+} separators)
当然,这意味着动态类型给程序员认真地注释和记录事物带来了更大的负担。 并维护该文档。
另一方面,静态类型会迫使我在split()
函数上维护正确的类型注释,即使它的实现发生了变化,即使我很着急,我的IDE也会帮助我自动重构它们。 地球上没有IDE可以提供与维护注释相同的帮助!
现在考虑一下这种额外的表现力还能给我带来什么。 假设Foo
和Bar
没有共享有趣的通用超类型。 在地球上任何动态语言中,我都可以编写以下代码:
class Super {
function fun() { return Foo(); }
}
class Sub extends Super {
function fun() { return Bar(); }
}
但是,如果我确实写过这样的话,我的队友可能会想扼杀我! 对于调用fun()
的客户端仅处理Foo
的情况而言,这里存在太多的潜力,而没有意识到有时它会返回Bar
。 通常,大多数程序员将避免使用上述代码,并确保fun()
始终返回与继承密切相关的类型。
作为第二个示例,请考虑Java类型系统中的一个众所周知的漏洞: null
。 在Java中,我可以这样写:
class Super {
public Foo fun() { return Foo(); }
}
class Sub extends Super {
public Foo fun() { return null; }
}
同样,这是Java开发人员经常避免的事情,特别是对于公共API。 由于它不是fun()
签名的一部分,它可能返回null
,因此调用方可能会明显地不处理这种情况,从而导致NPE处于更远的位置,因此通常最好的方法是抛出异常而不是从属于公共API的方法返回null
。
现在让我们考虑锡兰。
class Super() {
shared default Foo? fun() => Foo();
}
class Sub() extends Super() {
shared actual Null fun() => null;
}
由于fun()
可以返回null
的事实是其签名的一部分,并且由于类型系统强迫任何调用者考虑返回null值的可能性,因此此代码绝对没有错。 因此,在Ceylon中,通常最好将函数或方法定义为仅返回null
而不是抛出防御性异常。 因此,我们得出一个明显的悖论:
通过更加严格地处理null,我们使null更加有用。
现在,由于锡兰的“可空类型”只是联合类型的特例,因此我们可以将这种观察推广到其他联合类型。 考虑:
class Super() {
shared default Foo|Bar fun() => Foo();
}
class Sub() extends Super() {
shared actual Bar fun() => Bar();
}
同样,此代码绝对没有错。 任何调用Super.fun()
客户端都必须处理两种可能的具体返回类型。
我的意思是,通过添加静态类型,我们在表达能力上获得了一定的收益。 没有静态类型本来会很容易出错的事情已经变得完全安全并且完全可以自我记录。
翻译自: https://www.javacodegeeks.com/2013/12/a-paradox-about-typesafety-and-expressiveness.html