函数式go语言 gui开发GUI是不是自虐

函数式编程语言天生就慢吗?
发表于 11:25|
来源CSDN编译|
作者张红月
摘要:近期,函数式编程得到了越来越多的关注,Lisp不仅重获青春还涌现出了一批新函数式编程语言。因此开发者们对函数式编程语言的运行快慢各抒己见,展开激烈讨论。本文将和大家一起讨论,函数式编程语言真的就慢吗?
由于函数式语言需要基础架构支持,这样不可避免地增加了学习汇编理论知识的成本。一流的词法闭包只有在配合垃圾回收时才能良好地工作,因为它允许值越界。
函数式语言:过度分配
请注意自己的选择。C在基准套件中扮演最小公分母,限制是可以实现的。如果在比较C语言与函数式编程语言上有一个基准,那么它肯定是一个非常简单的程序。按理说这么简单,它是没有什么实际意义的。仅仅把C作为基准的话,那么在解决更复杂的问题上,它是没有什么实际可行的解决方案。
这方面最明显的例子就是并行性。如今,多核已成为一种主流,甚至连我的手机也是多核的。在C语言中,要想实现多核并行是相当困难的,但是在函数式编程语言中会却容易实现(我比较喜欢F#)。其它例子还包括持久化数据结构,例如撤消缓冲区与纯函数数据结构是微不足道的,但在命令式语言上,这却是个非常大的工作量。
函数式语言看起来比C语言要慢,因为你仅看到基准代码在C语言里很容易去编写并且你永远不会明白基准比任务更耐人寻味,从函数式语言到Excel。然而,目前你已经能正确的识别出函数式语言的最大瓶颈是什么:过度的分配率。为什么函数式语言分配会如此严重的原因就是它被拆分成历史和内在的。
从历史上看,Lisp实现已经做了50年的装箱工作。这个特点也渗透到许多其它编程语言中,类似Lisp的中间件表示。这些年来,语言实现不断地采取装箱这种方式快速实现并发。在面向对象语言中,默认堆分配每个对象,即使明显可以采用堆栈分配。提高效率的负担是推到垃圾收集器并且在建设垃圾收集器性能上做一些努力,使它能够达到或者最大可能接近堆栈分配,通常是使用bump-allocating托管实现。我认为,应该投入更多的精力来研究函数语言设计,减少装箱和垃圾收集器设计,从而优化不同的需求。
分代式垃圾收集器
分代式垃圾收集器对语言来说,是很棒的,因为堆可以分配很多并且他们的速度也和堆栈分配差不多一样快,但是会增加其他地方的开销。如今的程序越来越多地使用像队列似地的数据结构(例如并发编程)并且让分代式垃圾收集产生一些病态行为。如果队列中的某项活得比第一代长,那么它们都会被做标记,然后所有引用的旧位置将会得到更新并且被收藏。这个大概要比它们需要的慢3倍(例如比C语言)。标记区域的收集器有可能解决这个问题,像(2002)和(2008),因为托管已经被替换成一个区域,可以被收集,就好像是一个托管所,如果它包含大部分可及值,可以被另一个区域替换并且留下时间,直到它包含一些遥不可及的值。
尽管已经存在的C++,还有Java的发明人采用泛型来消除这些错误,但这些都导致了不必要的装箱。例如,我构建一个简单的哈希表,在.NET上面的速度比JVM要快17倍。原因就是.NET并没有犯这个错误(它采用具体化泛型)并且.NET还有值类型。实际上,我认为是Lisp让Java变慢。
装箱、拆箱
所有现代式的函数式语言都是过分依赖装箱。基于JVM语言,像Clojure和Scala别无选择,因为VM甚至不能表达值类型。Ocaml(Objective Caml)在早期就揭示了类型信息,在它的编译过程和经常使用整数类型进行装箱标记并且在运行时去处理多态性。因此,Ocaml常常作为私有浮点数字被装入箱中并且一直是盒元组。在Ocaml中一个三重的字节就是一个由指针(有一个隐藏的标签嵌入在里面并且在运行时会被反复检查)和一个64位的堆上分配块头与192位的主体包含三个标记的63字节整数(3个标签,在运行时会被反复检查)。这显然是疯了。
在函数式语言上,有关拆箱优化工作已经完成但它并未真正获得牵引力。例如Mlton编译器对于ML标准来说,是一个全程序优化编译器并且很擅长拆箱优化工作。不幸的是,在运行时间之前和&长&编译时间(在现代机器上低于1秒)阻止人们使用它。
唯一的主要平台已经打破了这个趋势,但令人惊讶的是.NET却是个例外。尽管有一个Dictionary类可以高度优化键和值。微软的员工,比如Eric Lippert就继续强调,这一点很重要并且性能特点不是来源于它们内部拆箱特征。Eric的理解似乎已经被证明是错误的:越来越多的.NET程序员青睐拆箱而不是值传递。事实上,大多数结构是不可变的,因此,引用透明在值传递和引用传递之间并没有什么语义差别。性能是可见的并且结构可以提供大量的性能改进。性能结构甚至可以保存堆栈溢出并且结构常常用来避免GC延迟在商业软件上面,比如。
函数式与命令式
重分配的函数式语言的另一个原因是与生俱来的。命令式数据结构像哈希表结构使用内在巨大的整体数组。如果这些巨大的内部数组一直持续使用,将需要不断复制和更新。所以纯函数式数据结构比如平衡二叉树分裂成许多小堆,分裂成块为了便于从一个版本集合到另一个版本。
Clojure采用了一个非常巧妙的花招来解决这个问题,当集合例如dictionaries在初始化时被写,然后进行读取。在这个例子中,初始化可以使用突变来建立&幕后&结构。然而,这并不会有助于增量更新并且由此产生的集合在读取数据方面仍然比较命令式等价物慢。当然纯函数式语言在数据持久化方面明显要比命令式强。然后,很少的实际应用程序受益于持久化实践,所以这并不算是什么优势。因此,把非纯函数式语言降到命令式风格,这样就可以毫不费力的从总受益。
原文来自:
本文为CSDN编译整理,未经许可不得转载。如需转载请联系。&
推荐阅读相关主题:
网友评论有(0)
CSDN官方微信
扫描二维码,向CSDN吐槽
微信号:CSDNnews
相关热门文章developerWorks 社区
Java™ 即使不打算立刻改用函数式语言(比如 Scala 或 Clojure),Java 开发人员也应该了解函数式范式。随着时间的推移,所有主流语言都将变得更加函数化;Neal Ford 将在本期文章中探讨其中的原因。
, Director / Software Architect / Meme Wrangler, ThoughtWorks Inc.
Neal Ford 是一家全球性的 IT 咨询公司 ThoughtWorks 的主管、软件架构师和 Meme Wrangler。他还设计并编写了一些应用程序、教材、杂志文章、课件和视频/DVD演示文稿,他是多种技术书籍的作者或编辑,其中包括最近出版的这本 。他的工作重点是设计和构建大型企业级应用程序。他还是全球范围开发者大会上的一位国际知名的演讲者。您可以查看 。
到目前为止,在本系列的每期文章中,我都说明了为什么理解函数式编程非常重要。但是,有些原因是在多期文章中进行说明的,只有在综合思路的更大背景中,才可以完全了解这些原因。在本期文章中,我会探讨函数式编程方兴未艾的所有原因,并综合前几期文章中的一些个人经验教训。
在计算机科学短短的发展历史中,技术的主流有时会产生分支,包括实用分支和学术分支。20 世纪 90 年代的 4GL(第四代语言)是一个实用分支,而函数式编程是来自学术界的一个示例。每隔一段时间,都会有一些分支加入主流,函数式编程目前也是这种情况。函数式语言不仅在 JVM 上刚刚崭露头脚(其中两个最有趣的新语言是 Scala 和 Clojure),在 .NET 平台上也是才开始得到应用,在 .NET 平台上,F# 是头等公民。为什么所有平台都如此欢迎函数式编程?答案是,随着时间的推移,随着运行时都要能够处理更多的繁忙工作,开发人员已经能够将日常任务的更多控制权割让给它们。
割让控制权
在 20 世纪 80 年代初,在我上大学的时候,我们使用一个被称为 Pecan Pascal 的开发环境。其独特的特性是,相同的 Pascal 代码可以在 Apple II 或 IBM PC 上运行。Pecan 工程师使用某个称为 “字节码” 的神秘东西实现了这一壮举。开发人员将 Pascal 代码编译为 “字节码”,它可以在每个平台本地编写的 “虚拟机” 上运行。这是一个可怕的体验!所生成的代码慢得让人痛苦,甚至简单的类赋值也非常缓慢。当时的硬件还没有准备好迎接这个挑战。
在发布 Pecan Pascal 之后的十年,Sun 发布了 Java,Java 使用了相同的架构,对于 20 世纪 90 年代中期的硬件环境,运行该代码显得有些紧张,但最终取得了成功。Java 还增加了其他开发人员友好的特性,如自动垃圾收集。使用过像 C++ 这样的语言之后,我再也不想在没有垃圾收集的语言中编写代码。我宁愿花将时间花在更高层次上的抽象上,思考解决复杂业务问题的方法,也不愿意在内存管理等复杂的管道问题上浪费时间。
Java 缓解了我们与内存管理的交互;函数式编程语言使我们能够用高层次的抽象取代其他核心构建块,并更注重结果而不是步骤。
结果比步骤更重要
函数式编程的特点之一是存在强大的抽象,它隐藏了许多日常操作的细节(比如迭代)。我在本系列文章中一直使用的一个示例是数字分类:确定某个数字是 perfect、abundant 还是 deficient(完整的定义参见 )。清单 1 中显示的 Java 实现可以解决这个问题:
清单 1. 自带缓存总数的 Java 数字分类器import static java.lang.Math.
public class ImpNumberClassifier {
private Set&Integer& _
private int _
private int _
public ImpNumberClassifier(int number) {
_factors = new HashSet&Integer&();
_factors.add(1);
_factors.add(_number);
private boolean isFactor(int factor) {
return _number % factor == 0;
private void calculateFactors() {
for (int i = 1; i &= sqrt(_number) + 1; i++)
if (isFactor(i))
addFactor(i);
private void addFactor(int factor) {
_factors.add(factor);
_factors.add(_number / factor);
private void sumFactors() {
calculateFactors();
for (int i : _factors)
private int getSum() {
if (_sum == 0)
sumFactors();
public boolean isPerfect() {
return getSum() - _number == _
public boolean isAbundant() {
return getSum() - _number & _
public boolean isDeficient() {
return getSum() - _number & _
} 中的代码是典型的 Java 代码,它使用迭代来确定和汇总系数。在使用函数式编程语言时,开发人员很少关心细节(比如迭代,由 calculateFactors() 使用)和转换(比如汇总一个列表,该列表由 sumFactors() 使用),宁愿将这些细节留给高阶函数和粗粒度抽象。
粗粒度的抽象
用抽象来处理迭代等任务,使得需要维护的代码变得更少,因此可能出现错误的地方也就更少。清单 2 显示了一个更简洁的数字分类器,用 Groovy 编写,借用了 Groovy 的函数风格方法:
清单 2. Groovy 数字分类器import static java.lang.Math.sqrt
class Classifier {
def static isFactor(number, potential) {
number % potential == 0;
def static factorsOf(number) {
(1..number).findAll { isFactor(number, it) }
def static sumOfFactors(number) {
factorsOf(number).inject(0, {i, j -& i + j})
def static isPerfect(number) {
sumOfFactors(number) == 2 * number
def static isAbundant(number) {
sumOfFactors(number) & 2 * number
def static isDeficient(number) {
sumOfFactors(number) & 2 * number
} 中的代码使用很少的代码完成
的所有工作(减去缓存总数,这会重新出现在下面的示例中)。例如,用于确定 factorsOf() 中的系数的迭代消失了,替换为使用 findAll() 方法,它接受一个具有我的筛选器条件的代码块(一个高阶函数)。Groovy 甚至允许使用更简洁的代码块,它允许单参数块使用
it 作为隐含参数名称。同样,sumOfFactors() 方法使用了 inject(),它(使用 0 作为种子值)将代码块应用于每个元素,将每个对减少为单一的值。{i, j -& i + j} 代码块返回两个参数的总和;每次将列表 “折叠” 成一个对时,都会应用此块,产生总和。
Java 开发人员习惯于框架 级别的重用;在面向对象的语言中进行重用所需的必要构件需要非常大的工作量,他们通常会将精力留给更大的问题。函数式语言在更细化的级别提供重用,在列表和映射等基本数据结构之上通过高阶函数提供定制,从而实现重用。
少量数据结构,大量操作
在面向对象的命令式编程语言中,重用的单元是类以及与这些类进行通信的消息,这些信息是在类图中捕获的。该领域的开创性著作是 Design Patterns: Elements of Reusable Object-Oriented Software(参阅 ),至少为每个模式提供一个类图。在 OOP 的世界中,鼓励开发人员创建独特的数据结构,以方法的形式附加特定的操作。函数式编程语言尝试采用不同的方式来实现重用。它们更喜欢一些关键的数据结构(如列表、集和映射),并且在这些数据结构上采用高度优化的操作。传递数据结构和高阶函数,以便 “插入” 这种机制,针对某一特定用途对其进行定制。例如,在
中,findAll() 方法接受使用一个代码块作为 “插件” 高阶函数(该函数确定了筛选条件),而该机制以有效方式应用了筛选条件,并返回经过筛选的列表。
函数级的封装支持在比构建自定义类结构更细的基础级别上进行重用。此方法的优势之一已经体现在 Clojure 中。最近,库中的一些巧妙创新重写了 map 函数,使它可以自动并行化,这意味着所有映射操作都可以受益于没有开发人员干预的性能提升。
例如,考虑一下解析 XML 的情况。大量的框架可用于在 Java 中完成这个任务,每个框架都有自定义的数据结构和方法语义(例如,SAX 与 DOM)。Clojure 将 XML 解析为一个标准的 Map 结构,而不是强迫您使用自定义的数据结构。因为 Clojure 中包含大量与映射配合使用的工具,如果使用内置的列表理解函数 for,那么执行 XPath 样式的查询就会很简单,如清单 3 所示:
清单 3. 将 XML 解释为 Clojure(use 'clojure.xml)
(def WEATHER-URI "/forecastrss?w=%d&u=f")
(defn get-location [city-code]
(for [x (xml-seq (parse (format WEATHER-URI city-code)))
:when (= :yweather:location (:tag x))]
(str (:city (:attrs x)) "," (:region (:attrs x)))))
(defn get-temp [city-code]
(for [x (xml-seq (parse (format WEATHER-URI city-code)))
:when (= :yweather:condition (:tag x))]
(:temp (:attrs x))))
(println "weather for " (get-location ) "is " (get-temp ))
中,我访问雅虎的气象服务来获取某个给定城市的气象预报。因为 Clojure 是 Lisp 的一个变体,所有从内部读取是最简单的。对服务端点的实际调用发生在 (parse (format WEATHER-URI city-code)) 上,它使用了 String 的 format() 函数将 city-code 嵌入字符串。列表理解函数 for 放置了解析后的 XML,使用 xml-seq 将它投放到名称为 x 的可查询映射中。:when 谓词确定了匹配条件;在本例中,我要搜索一个标签(转换成一个 Clojure 关键字) :yweather:condition。
如欲了解从数据结构中读取值所用的语法,那么查看该语法中包含的内容会非常有用。在解析的时候,气象服务的相关调用会返回在此摘录中显示的数据结构:
({:tag :yweather:condition, :attrs {:text Fair, :code 34, :temp 62, :date Tue,
am EST}, :content nil})
因为已经为了与映射配合使用而优化了 Clojure,所以关键字在包含它们的映射上成为了函数。在
中,对 (:tag x) 的调用是一个缩写,它等同于 “从存储在 x 中的映射检索与 :tag 键对应的值”。因此,:yweather:condition 产生与该键关联的映射值,其中包括我使用相同语法从中提取 :temp 的 attrs。
最初,Clojure 中令人生畏的细节之一是:与映射和其他核心数据结构进行交互的方法似乎有无限多种。然而,它反映了这样一个事实:在 Clojure 中,大多数内容都尝试解决这些核心的、优化的数据结构。它没有将解析的 XML 困在一个独特的框架中,相反,它试图将其转换为一个已存在相关工具的现有结构。
对基础数据结构的依赖性的优点体现在 Clojure 的 XML 库中。为了遍历树形结构(如 XML 文档),1997 年创建了一个有用的数据结构,名为 zipper(参阅 )。zipper 通过提供坐标系方向,让您可以结构性地导航树。例如,可以从树的根开始,发出 (-& z/down z/down z/left) 等命令,导航到第二级的左侧元素。Clojure 中已经有现成的函数可将解析的 XML 转换为 zipper,在整个树形结构中实现一致的导航。
新的、不同的工具
函数式编程提供了新的工具类型,以优雅的方式解决棘手的问题。例如,Java 开发人员不习惯尽能延迟生成其值的惰性 数据结构。而未来的函数式语言将对这种高级特性提供支持,一些框架将此功能加装到 Java 中。例如,清单 4 所示的数字分类器版本使用了 Totally Lazy 框架(参阅 ):
清单 4. Java 数字分类器通过 Totally Lazy 使用惰性和函数式数据结构import com.googlecode.totallylazy.P
import com.googlecode.totallylazy.S
import static com.googlecode.totallylazy.Predicates.
import static com.googlecode.totallylazy.numbers.Numbers.*;
import static com.googlecode.totallylazy.predicates.WherePredicate.
public class Classifier {
public static Predicate&Number& isFactor(Number n) {
return where(remainder(n), is(zero));
public static Sequence&Number& getFactors(final Number n){
return range(1, n).filter(isFactor(n));
public static Sequence&Number& factors(final Number n) {
return getFactors(n).memorise();
public static Number sumFactors(Number n){
return factors(n).reduce(sum);
public static boolean isPerfect(Number n){
return equalTo(n, subtract(sumFactors(n), n));
public static boolean isAbundant(Number n) {
return greaterThan(subtract(sumFactors(n), n), n);
public static boolean isDeficient(Number n) {
return lessThan(subtract(sumFactors(n), n), n);
Totally Lazy 增加了惰性集合和流畅接口方法,大量使用静态导入,使代码具有可读性。如果您羡慕下一代语言中的某些特性,那么一些研究可能会提供可以解决某个特定问题的特定扩展。
让语言迁就问题
大多数开发人员都将他们的工作误解为接受一个复杂的业务问题,将它转换成 Java 等语言。他们的这种误解是因为 Java 并不是一种特别灵活的语言,它迫使您让自己的想法适应于已经存在的刚性结构。但是,当开发人员使用可塑语言时,他们看到了让语言迁就问题,而不是让问题迁就语言的机会。像 Ruby(它为领域特定语言 (DSL) 提供了比主流更友好的支持)等语言证明了这种潜在可能。现代函数式语言甚至走得更远。Scala 旨在协调内部 DSL 的托管,并且所有 Lisp(包括 Clojure)都可以提供无与伦比的灵活性,使开发人员能够让语言适应问题。例如,清单 5 使用了 Scala 中的 XML 基元来实现
的天气示例:
清单 5. Scala 的 XML 语法修饰import scala.xml._
import java.net._
import scala.io.Source
val theUrl = "/forecastrss?w=&u=f"
val xmlString = Source.fromURL(new URL(theUrl)).mkString
val xml = XML.loadString(xmlString)
val city = xml \\ "location" \\ "@city"
val state = xml \\ "location" \\ "@region"
val temperature = xml \\ "condition" \\ "@temp"
println(city + ", " + state + " " + temperature)
Scala 是为获得可塑性而设计的,它支持操作符重载和隐式类型等扩展。在
中,Scala 被扩展为可以使用 \\ 操作符支持类似 XPath 的查询。
与语言的趋势相一致
函数式编程的目标之一是最大程度地减少可变状态。在
中,有两种类型的共享状态清单。_factors 和 _number 都存在,它们使代码测试变得更容易(编写原代码版本是为了说明最大可测试性),并可以折叠成更大的函数,从而消除它们。但是,_sum 是因为各种原因而存在。我预计,这段代码的用户可能需要检查多个分类。(例如,如果一个完美的检查失败,那么下一次我可能会检查百分比。)合计系数总数的操作可能很昂贵,所以我为它创建了一个经过惰性初始化的访问器。在第一次调用时,它会计算总和,并将它存储在 _sum 成员变量中,以便优化未来的调用。
像垃圾收集一样,现在缓存也可以降级用于语言。 中的 Groovy 数字分类器忽略了
中总数的惰性初始化。如果想要实现同样的功能,可以修改分类器,如清单 6 所示:
清单 6. 手动添加一个缓存class ClassifierCachedSum {
private sumCache
ClassifierCachedSum() {
sumCache = [:]
def sumOfFactors(number) {
if (sumCache.containsKey(number))
return sumCache[number]
def sum = factorsOf(number).inject(0, {i, j -& i + j})
sumCache.putAt(number, sum)
return sum
// ... other code omitted
在最新版的 Groovy 中, 中的代码不再是必要的。考虑使用清单 7 中的改进版的分类器:
清单 7. 备忘数字分类器class ClassifierMemoized {
def static dividesBy = { number, potential -&
number % potential == 0
def static isFactor = dividesBy.memoize()
def static factorsOf(number) {
(1..number).findAll { i -& isFactor.call(number, i) }
def static sumFactors = { number -&
factorsOf(number).inject(0, {i, j -& i + j})
def static sumOfFactors = sumFactors.memoize()
def static isPerfect(number) {
sumOfFactors(number) == 2 * number
def static isAbundant(number) {
sumOfFactors(number) & 2 * number
def static isDeficient(number) {
sumOfFactors(number) & 2 * number
任何纯函数(没有副作用的函数)都可以备忘,比如
中的 sumOfFactors() 方法。备忘函数允许运行时缓存重复出现的值,从而消除手工编写缓存的需要。事实上,请注意执行实际工作的 getFactors() 和 factors() 方法之间的关系,该方法是备忘版本的 getFactors()。Totally Lazy 还为 Java 增加了备忘功能,这是反馈到主流中的另一个高级函数特性。
由于运行时获得了更多的能力并且有多余的开销,开发人员可以将繁忙的工作割让给语言,将我们解放出来,去思考更重要的问题。Groovy 中的备忘功能就是众多示例中的一个;因为基础运行时允许这样做,所有现代语言都添加了函数式构造,包括 Totally Lazy 等框架。
因为运行时的能力变得更强,并且语言获得了更强大的抽象,所以开发世界变得更加函数化,这使开发人员可以花费更多的时间来思考结果的影响,而不是思考如何生成结果。由于高阶函数等抽象出现在语言中,它们将成为高度优化的操作的自定义机制。您不需要创建框架来处理问题(如 XML),您可以将其转换成您已经可以使用工具来处理的数据结构。
随着第 20 期文章的发布, 将告一段落,我将准备开始一个新的系列,探索下一代的 JVM 语言。 会让您对不久的将来有一个大致了解,并帮助您对必须投入新语言学习的时间作出明智选择。
(Neal Ford,O'Reilly Media,2008 年):Neal Ford 撰写的一本书籍,讨论了能够帮助您提高编码效率的工具和实践。:Scala 是运行在 JVM 上的一种现代函数式语言。:Clojure 是运行在 JVM 上的一种现代函数式 Lisp。(Erich Gamma 等,Addison-Wesley,1994):Gang of Four 有关设计模式的经典著作。
:阅读 Wikipedia 的 zipper 数据结构定义。:查找有关 Java 编程的方方面面的数百篇文章。
:Totally Lazy 是一个函数式框架,它将惰性和流畅性添加到了 Java 中。 下载
,并开始使用来自 DB2®、Lotus®、Rational®、Tivoli® 和 WebSphere® 的应用程序开发工具和中间件产品。 加入 。探索由开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户进行交流。
developerWorks: 登录
标有星(*)号的字段是必填字段。
保持登录。
单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件。
在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。
所有提交的信息确保安全。
选择您的昵称
当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。昵称长度在 3 至 31 个字符之间。
您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。
标有星(*)号的字段是必填字段。
(昵称长度在 3 至 31 个字符之间)
单击提交则表示您同意developerWorks 的条款和条件。 .
所有提交的信息确保安全。
文章、教程、演示,帮助您构建、部署和管理云应用。
立即加入来自 IBM 的专业 IT 社交网络。
免费下载、试用软件产品,构建应用并提升技能。
static.content.url=/developerworks/js/artrating/SITE_ID=10Zone=Java technologyArticleID=936767ArticleTitle=函数式思维: 为什么函数式编程越来越受关注publish-date=后使用快捷导航没有帐号?
& & & & 函数式编程语言
Powered by Discuz!
& Comsenz Inc.

我要回帖

更多关于 易语言gui 的文章

 

随机推荐