我是一位失败的诗人。 也许每个小说家都想先写诗,发现自己不能写诗,然后再尝试短篇小说,这是继诗歌之后要求最高的形式。 失败了,只有这样他才能从事新颖的写作。
—威廉·福克纳(William Faulkner),诺贝尔奖获得者
并非所有作家-甚至不是伟大的作家-都打算在每种媒介中写作。 同样,对于程序员来说,某些编程语言比其他编程语言更自然。 有些开发人员是天生的C程序员,有些则与Lisp有天生的联系,而另一些人则对Perl发誓。 没有一种语言能够与每种开发人员的偏好相匹配的事实有助于解释为什么存在这么多计算机语言。 Java.next的含义是,没有一种语言会主导整个领域,因为没有一种语言适合所有人。
Java语言似乎是一个反例。 但是Java的统治地位来自于一系列独特的情况- 布鲁斯·泰特 ( Bruce Tate )在他的著作《 超越Java 》中将其称为“完美风暴”。 当Java在1990年代中期发布时,它面临着采用方面的艰巨挑战。 它比当时流行的编译语言慢。 这是对内存的渴望(当时内存价格暂时上涨)。 而且它并不特别适合当时占主导地位的客户端/服务器开发风格。 Java唯一的两个节省之处是它的相对易用性(通过诸如垃圾收集之类的设施)和Applet,这在当时是独一无二的。 如果情况仍然是静态的,那么Java可能无法幸免。
但是Java和当时新的万维网是绝配,特别是当Servlet API流行时。 突然,服务器端部署模型减轻了Java的许多缺点。 这些因素(硬件,Web,范式)的组合是Tate的完美风暴:开发人员需要新的工具来对Web进行编程,服务器端Java减轻了内存限制,并提供了用于构建健壮的Web应用程序的简化模型。 通过在合适的时间,在合适的位置准备好并得到一家大型公司(Sun)的支持,Java成为了软件行业中的主导力量。
对于另一种语言,一系列情况不太可能如此巧妙地排列。 我们进入了一个多语言的计算机语言世界,这一趋势正在持续增长。 尝试识别与Java具有相同影响的下一种语言注定会失败。 在研究要采用的Java.next语言时,请着重于与您产生共鸣的方面,而不是寻求压倒性的普及。
多范式语言
许多现代语言都支持几种编程范例:面向对象,元编程,功能,过程等。 在Java.next语言中,Groovy和Scala都是多范式。 Groovy是一种通过库进行功能扩展的面向对象语言。 Scala是一种混合的面向对象和功能性语言,重点是功能性编程偏好,例如不变性和惰性。
多范式语言提供了强大的功能,使您能够混合和匹配范式以紧密匹配问题。 许多开发人员对版本8之前的Java的限制感到不满。像Groovy这样的语言提供了更多的功能,包括元编程和函数构造。
尽管多范式语言功能强大,但在大型项目上仍需要更多的开发人员约束。 因为该语言支持许多不同的抽象和哲学,所以孤立的开发人员小组可以在库中创建完全不同的变体。 例如,代码重用在面向对象的世界中倾向于结构,在功能性世界中倾向于组合和高阶函数。 在设计公司的Customer
API时,必须确定最佳样式,并确保团队中的每个人都同意(并遵守)它。 从Java迁移到Ruby的许多开发人员都遇到了此问题,因为Ruby是一种多范式语言。 C ++是另一种多范式语言,给许多企图跨越程序和面向对象的笨拙(常常是无意间)的项目造成了痛苦。
一种解决方案是依靠工程纪律来确保项目中的所有开发人员都朝着同一目标努力。 许多开发人员对使用元编程对核心类进行普遍修改持谨慎态度。 例如,某些测试库向Object
添加方法以允许广义范围内的断言。 单元测试使您能够对复杂的扩展进行精确的了解,从而减轻了对未知副作用的恐惧。
包括Clojure在内的某些语言主要采用一种范例,而实际地支持另一种范例,从而施加了更多的纪律性。 Clojure绝对是JVM的功能Lisp。 您可以从基础平台与类和方法进行交互(并且可以根据需要创建自己的类),但是Clojure的主要支持是功能强大的范例,如不变性和惰性。
杀手级功能:功能编程
对于大多数开发人员来说,函数式编程是最重要的未来语言特性。 我分几期介绍了Java.next语言的功能方面。 功能范例有效性的关键在于以更高的抽象水平表达思想的能力。
在“ 备忘和撰写 ”部分中,我将命令性indexOfAny()
方法(来自Apache Commons StringUtils
库)转换为Clojure,从而产生了一个更短,更简单但更通用的函数。 Clojure对启动者非常可读,但是对于非Lisp开发人员来说,这看起来很奇怪。 Scala旨在提高Java开发人员的可读性。 清单1显示了相同的indexOfAny()
方法,该方法indexOfAny()
转换为Scala而不是Clojure。
清单1.一个Scala indexOfAny()
实现
def indexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Option[Int] = {
def indexedInput = (0 until input.length).zip(input)
val result = for (char <- searchChars;
pair <- indexedInput;
if (char == pair._2)) yield (pair._1)
if (result.isEmpty)
None
else
Some(result.head)
}
indexOfAny
方法的目的是在第一个参数内返回第二个参数中传递的任何字符的索引位置。 在清单1中 ,我首先通过基于输入字符串的长度构建一个数字顺序列表来生成indexedInput
。 然后,我使用Scala中内置的zip()
函数,它将两个列表“ zip()
”在一起。 例如,如果我有输入字符串zabycdxx
,结果indexedInput
样子Vector((0,z), (1,a), (2,b), (3,y), (4,c), (5,d), (6,x), (7,x))
。
在拥有indexedInput
集合之后,我将使用for
理解来替换原始版本中的嵌套循环。 首先,我搜索searchChars
; 我在indexedInput
的对的第二部分中检查每个字符的存在(使用Scala速记对pair._2)
),如果匹配则返回对的索引部分( pair._1
)。 yield()
函数为返回列表生成值。
在Scala中,通常返回Option
而不是可能的null
,因此如果不存在结果,则返回None
否则返回Some
。 原始的indexOfAny()
方法仅返回第一个匹配字符的索引,因此我仅返回结果中的第一个元素( result.head
)。 在Clojure版本中,我返回所有匹配项的列表。 转换Scala版本以进行相同的操作很容易,如清单2所示。
清单2. indexOfAny
返回所有匹配项
def lazyIndexOfAny(input : Seq[Char], searchChars : Seq[Char]) : Seq[Int] = {
def indexedInput = (0 until input.length).zip(input)
for (char <- searchChars;
pair <- indexedInput;
if (char == pair._2)) yield (pair._1)
}
在清单2中 ,返回值是匹配项列表,而不仅仅是第一个匹配项。 例如, lazyIndexOfAny("zzabyycdxx", "by")
是Vector(3, 4, 5)
,它与每个目标字符的输入字符串内的索引匹配。
函数式编程语言使您能够使用功能更强大的构建块(例如map()
优先于循环map()
在更高的抽象层次上工作。 当您摆脱了对底层代码细节的关注时,便可以更加清晰地关注与之相关的问题。
功能金字塔
计算机语言类型通常沿着两个轴存在,如图1所示,其中强与弱,动态与静态。
图1.语言键入特征
强类型变量“知道”其类型,从而启用反射和实例检查,并且它们保留了该知识。 弱类型语言对它们指向的内容的理解度较低。 例如,C是一种静态的,弱类型的语言:C中的变量实际上是一些位的集合,可以用各种方式来解释,这给各地C开发人员带来的欢乐和恐惧(有时是同时)。
Java是强类型的静态类型。 声明变量时,必须指定变量类型,有时需要重复指定。 Java逐渐引入了类型推断,但是就类型的简洁性而言,它几乎不及任何Java.next语言那么远。 Scala,C#和F#也是强静态类型的,但是通过使用类型推断,它们的冗长程度要低得多。 很多时候,该语言可以识别适当的类型,从而减少了冗余。
这些区别自编程语言的早期时代就已经存在。 但是,方程式中引入了一个新的方面:函数式编程。
正如我在“ 函数式编码风格 ”中所说明的那样,函数式编程语言与命令式语言具有不同的设计理念。 命令式语言试图使突变状态更容易,并为此目的提供了许多功能。 功能语言试图使可变状态最小化,并构建更多通用机器。 但是功能并不决定打字系统,如图2所示。
图2.函数式编程语言
函数式编程语言依赖(有时会坚持)不变性。 语言之间的主要区别现在不是动态还是静态。 它势在必行,而不是功能,对我们构建软件的方式产生了有趣的影响。
在2006年的我的博客中,我不小心重新使用了多语种编程一词,并赋予了它新的含义:利用现代运行时来创建混合和匹配语言而不是平台的应用程序。 重新定义是基于这样的认识,即Java和.NET平台之间支持200多种语言,另外还怀疑没有一种“真正的语言”可以解决所有问题。 借助现代化的托管运行时,您可以在字节码级别上自由混合和匹配语言,并针对特定工作使用最佳语言。
在发布博客文章之后,我的同事Ola Bini发表了后续文章,讨论了他的Polyglot金字塔。 如图3所示,金字塔表明了人们在多语言世界中构建应用程序的方式。
图3.比尼的金字塔
在Bini的倒金字塔中,他建议在最底层使用更静态的语言,其中可靠性是最高优先级。 接下来,他建议为应用程序层使用更动态的语言,并使用更简单的语法来构建诸如用户界面之类的东西。 最后,最重要的是领域特定语言(DSL),由开发人员构建,以封装简洁重要的领域知识和工作流程。 通常,DSL以动态语言实现,以在这方面利用其某些功能。 Bini的目标是在基岩层获得更多的确定性,并在最高层附近增加灵活性。
Bini的金字塔为我的原始文章增添了巨大的见识。 但是在随后的几年中,情况发生了变化。 我现在认为打字更受开发人员的青睐,而转移了重要特征:功能性与命令性。 我的新功能金字塔显示在图4中。
图4.功能金字塔
我们渴望的弹性不是来自静态类型,而是来自底层的功能概念。 如果您所有繁重的核心API(例如数据访问和集成)都可以假定是不变的,那么所有这些代码将更加简单。 当然,无处不在改变了我们构建数据库和其他基础架构的方式,但是结果是更好的核心稳定性。
在功能核心之上,使用命令性语言来处理工作流,业务规则,UI和系统其他部分(开发人员优先考虑的工作)。 与原始金字塔一样,DSL位于最上面,用于相同的目的。 但是,我也相信DSL将贯穿我们系统的所有层,一直到最底层。 可以轻松地用诸如Scala(功能性,静态强类型)和Clojure(功能性,动态强类型)之类的语言编写DSL来以简洁的方式捕获重要的概念就说明了这一点。
遵循此金字塔的构建应用程序代表了巨大的变化,但其含义令人着迷。 要了解各种可能性,请查看Datomic (商业产品)的体系结构。 Datomic是一个功能数据库,可保留每个更改的完整保真历史。 更新不会破坏数据。 它创建数据库的新版本。 您可以及时回滚数据库以查看过去的快照。 由于您始终拥有历史记录,因此诸如连续软件交付(依赖于及时地向前和向后滚动数据库的能力)之类的实践变得微不足道。 测试应用程序的多个版本变得很简单,因为您可以直接同步架构和代码更改。 Datomic是用Clojure构建的,在架构级别使用功能构造。 栈顶的含义是惊人的。
结论
这是Java.next的最后一部分。 我希望本系列文章能激发您的兴趣,以更深入地探讨我在前15期中所涉及的语言和概念。 自从我18个月前开始制作该系列以来,编程语言的领域已经发生了变化。 Java 8是Java.next领域的有力竞争者,最终添加了核心功能编程元素,这些元素将在未来几年内主导语言领域。 四种Java.next语言(Groovy,Scala,Clojure和Java 8)都拥有强大的社区和不断增长的用户基础,并且源源不断地出现了新的创新。 无论您选择哪种竞争者(或组合方式),对于JVM语言而言,前景都是一片光明。
翻译自: https://www.ibm.com/developerworks/java/library/j-jn16/index.html