名为雨捷的超神
新手的编程(求改错和指点)
展开Biu

刚开始接触JAVA,作为大2的我在大一接触过C++,对编程有一点基础,这学期开始学java

第一周老师让我们编已知三角形的三条边求面积和周长的题目

下面是我写的代码,不知道对不对,求大神指点和改错

import java.util.Scanner;

import java.Math.*;

public class Tria

{

double a,b,c,p;

double setA(int x)

{

a=x;

}

double setB(int y)

{

b=y;

}

double setC(int z)

{

c=z;

}

p=(a+b+c)/2;

double getArea()

{

return Math.sqrt(p*(p-a)*(p-b)*(p-c));

}

double getC()

{

return a+b+c;

}

}

class Task

{

public static void main(String args[])

{

System.out.println("请输入3个数确定三角形的三条边");

System.out.println("请确认任意两边之和大于第三边");

Tria triangle;

triangle=new Tria();

double x,y,z;

x=reader.nextDouble();

y=reader.nextDouble();

z=reader.nextDouble();

if(x+y>z)

{

if(x+z>y)

{

if(z+y>x)

{

triangle.setA(x);

triangle.setB(y);

triangle.setC(z);

double area=triangle.getArea();

double c=triangle.getC();

System.out.println("三角形的面积:"+area);

System.out.println("三角形的周长:"+c);

}

else

System.out.println("两边之和小于第三边");

}

else

System.out.println("两边之和小于第三边");

}

else

System.out.println("两边之和小于第三边");

}

}

[查看全文]
风音洛洛
轻舟过
来coursera上公开课吧~
展开Biu

我在上Algorithms, Part I,虽然学过算法,但是重新听Sedgewick讲一遍还是会有新的收获。

除了算法之外还有许多国外名校的计算机相关的课程啦

除了课程视频和课件之外,还有课后题和编程练习,个人觉得不错的,可以去学一学。

不过英语得好一点,不然听不懂视频讲什么,虽然有英文字幕。

所有课程列表:https://www.coursera.org/courses

另外还有一个国内的公开课交流的网站:http://52opencourse.com/

[查看全文]
三步页君
求救各位大触!怎样用c++实现BGLL算法!!!!
展开Biu

目前我已有了BGLL算法的各个源文件和头文件,但是就是好几个.h和.cpp要怎样连接在一起???啊!17~

而且能够接收数据得到结果啊啊啊啊 !29~

[查看全文]
yaonan
关于中文编程的段子的一个实现
展开Biu

网上这几天正在疯传一段用C#进行中文编程的段子,说一个程序员就职后,发现公司的大哥里把C#用中文进行了包装,不光是类,还有关键字也进行了中文化,正在苦恼是不是要继续在这个公司干下去。

这位大哥这种精神是否可嘉还真不好评价。对于没有意义的事情执着追求,是可嘉呢还是不可嘉,估计还是要看评论的人是谁。不过,人家自己的执着,别人也确实无资格评价。

还有所谓“意义”,恐怕也是因人而定义的。一个东西,对于为之付出了精力的人来说是有意义的,而对于其他人来说,即然与之没有交集,也就无资格置评。对于文中的小哥来说,喜欢的就留下搞搞明白,不喜欢的就走人吧。

只是这段中文化的代码,很有意思,上午试着用C#的lamda实现了一下,就所看到的代码而言,基本算是都实现了,现在我也可以用中文编程了。
下面是中文编程的示例,基本与网上那个段子差不多。

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace 中文编程

{

class Program

{

static void Main(string[] args)

{

// 逻辑判断演示....

判断.如果是(判断.真).则(() =>

{

Console.WriteLine("是");

}).否则(() =>

{

Console.WriteLine("否");

});

// 遍历器演示.....

登陆信息[] 所有登录信息 = new 登陆信息[100];

// ....

遍历器.计数遍历().从(0).到(99).每隔(1).执行((当前索引行) =>

{

所有登录信息[当前索引行] = new 登陆信息() { 姓名 = "姓名 " + 当前索引行.ToString() };

});

遍历器.枚举遍历<登陆信息>(所有登录信息).从(0).到(99).每隔(3).执行((当前索引行, 登录信息项) =>

{

Console.WriteLine(登录信息项);

});

数据库连接类 数据连接对象 = null;

//异常处理........

异常.对下列语句进行异常检测(() =>

{

数据连接对象 = 数据库连接类.连接到("10.1.138.35").用户名为("xxx").密码为("xxx");

数据连接对象.打开连接();

//...

throw new Exception("测试异常");

})

.发现异常则((异常对象) =>

{

//...

Console.WriteLine(异常对象);

})

.最终执行(() => {

// ...

数据连接对象.关闭连接();

});

}

}

public class 登陆信息

{

public string 姓名;

public override string ToString()

{

return "姓名" + 姓名;

}

}

}

关键字的包装:-----------------------------------------------------

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

namespace 中文编程

{

public class 判断

{

public const bool 真 = true;

public const bool 假 = false;

bool _b;

public static 判断 如果是(bool 条件)

{

return new 判断(){_b = 条件};

}

public 判断 则(Action act)

{

if (_b)

{

act();

}

return this;

}

public 判断 否则(Action act)

{

if (!_b)

{

act();

}

return this;

}

}

public class 遍历器

{

public static 枚举遍历器 *本站禁止HTML标签噢* 枚举遍历 *本站禁止HTML标签噢* (IEnumerable *本站禁止HTML标签噢* 枚举集合)

{

return new 枚举遍历器 *本站禁止HTML标签噢* (枚举集合);

}

public static 计数遍历器 计数遍历()

{

return new 计数遍历器() { };

}

}

public class 枚举遍历器 *本站禁止HTML标签噢*

{

protected IEnumerable *本站禁止HTML标签噢* _set;

protected int _iStartIndex;

protected int _iEndIndex;

protected int _Step;

public 枚举遍历器(IEnumerable *本站禁止HTML标签噢* 枚举集合)

{

this._set = 枚举集合;

}

public 枚举遍历器 *本站禁止HTML标签噢* 从(int 开始元素序号)

{

this._iStartIndex = 开始元素序号;

return this;

}

public 枚举遍历器 *本站禁止HTML标签噢* 到(int 结束元素序号)

{

this._iEndIndex = 结束元素序号;

return this;

}

public 枚举遍历器 *本站禁止HTML标签噢* 每隔(int 每隔步长)

{

this._Step = 每隔步长;

return this;

}

public void 执行(Action<int, T> 循环体方法)

{

int i = 0;

foreach (var e in _set)

{

if (i >= this._iStartIndex && i <= this._iEndIndex)

{

if ((i - this._iStartIndex) % this._Step == 0)

{

循环体方法(i, e);

}

}

i++;

}

}

}

public class 计数遍历器

{

protected int _iStartIndex;

protected int _iEndIndex;

protected int _Step;

public 计数遍历器 从(int 开始元素序号)

{

this._iStartIndex = 开始元素序号;

return this;

}

public 计数遍历器 到(int 结束元素序号)

{

this._iEndIndex = 结束元素序号;

return this;

}

public 计数遍历器 每隔(int 每隔步长)

{

this._Step = 每隔步长;

return this;

}

public void 执行(Action *本站禁止HTML标签噢* 循环体方法)

{

for (int i = this._iStartIndex; i <= this._iEndIndex; i += this._Step)

{

循环体方法(i);

}

}

}

public class 异常

{

Exception _ex = null;

public static 异常 对下列语句进行异常检测(Action 正常执行程序)

{

try

{

正常执行程序();

return new 异常() { _ex = null};

}

catch (Exception ex)

{

return new 异常() { _ex = ex};

}

}

public 异常 发现异常则(Action *本站禁止HTML标签噢* 异常处理程序)

{

if (this._ex != null)

{

异常处理程序(this._ex);

}

return this;

}

public 异常 最终执行(Action 最终处理程序)

{

最终处理程序();

return this;

}

}

}

数据库连接的包装:

using System;

using System.Collections.Generic;

using System.Linq;

using System.Text;

using System.Data.SqlClient;

namespace 中文编程

{

public class 数据库连接类

{

private string _sServer;

private string _sUID;

private string _sPassword;

private string _sDBName;

SqlConnection _sqlconn = null;

public static 数据库连接类 连接到(string 服务器名)

{

return new 数据库连接类() { _sServer = 服务器名 };

}

public 数据库连接类 用户名为(string 用户名)

{

_sUID = 用户名;

return this;

}

public 数据库连接类 密码为(string 密码)

{

_sPassword = 密码;

return this;

}

public 数据库连接类 数据库为(string 数据库名)

{

_sDBName = 数据库名;

return this;

}

public void 打开连接()

{

this._sqlconn = new SqlConnection(string.Format("Data Source={0};Initial Catalog={1};User ID={2};Password={3}", this._sServer, this._sDBName, this._sUID, this._sPassword));

this._sqlconn.Open();

}

public void 关闭连接()

{

this._sqlconn.Close();

}

}

}

[查看全文]
小狼
【windows】教你自制绿色软件
展开Biu

普通的安装版软件往往要给系统注册表里写入大量信息,安装许多后会严重拖慢系统速度,而绿色软件基本不会增加注册表体积。这样大批使用绿色软件代替安装软件后可以非常明显地加速系统。本文介绍一个很简单的软件绿化方法:

首先去下一个sandboxie,沙盘软件。它的作用类似于虚拟机,把运行的软件和真实系统隔离。只是实现途径不同。沙盘通过修改软件访问硬盘的路径实现。sandboxie会在指定的硬盘分区上建立一个‘sandbox’文件夹,当软件在沙盘中运行时,对系统文件的写入修改都是在sandbox文件夹中执行的。它不会干扰到真实系统的注册表和系统文件。

把你要绿色的软件**下来,新建一个沙盘。右击软件的exe安装文件,放到沙盘中运行,然后按着步骤一步一步来。安装完成后,打开sandbox文件夹,找到drive文件夹打开,然后会发现“C”"D""E"等文件夹。每一个文件夹代表一个模拟的分区。软件装到了那个分区上就点那个。然后找到“program files”文件夹,可以看到你的软件装到了沙盘里。把"program files"文件夹里的软件安装文件夹拷出沙盘,然后打开沙盘C盘里的windows\system32\,把里面的所有文件拷到拷出来的文件夹内。至此软件绿化完毕,双击可以试试能否运行。

使用此法可以把大部分软件绿化(输入法、驱动、虚拟机、杀毒软件貌似没办法),带来的好处就是一开机马上就能使用。

我把我2004年的古董笔记本里除了沙盘、杀毒软件、虚拟机和输入法外的所有软件(接近100款的可怕数字)绿化,几乎在桌面显示出来的同时就能访问硬盘,而且居然不会出手电筒图标。。。

部分软件可能提示缺少**文件,到网上查查缺少的文件然后把它**下来拷到安装目录即可解决问题。

[查看全文]
虹why
卡不列克圆舞曲 pascal语言的
展开Biu

嗯……貌似没人用pascal语言,但我现在只学了这门语言,以后肯定会换语言,先来一个比较经典的问题的程序:

卡布列克是一位数学家,他在研究数字时发现:任意一个不是用完全相同数字组成的四位数,如果对它们的每位数字重新排序,组成一个较大的数和一个较小的数,然后用较大数减去较小数,差不够四位数时补零,类推下去,最后将变成一个固定的数:6174,这就是卡布列克常数。 例如:4321-1234=3087 8730-378=8352 8532-2358=6174 7641-1467=6147 如果K位数也照此办理,它们不是变成一个数,就是几个循环的数。

program project1;

var n:string;

cd,gs,t,c2,r,f:longint;

s:array[1..100] of string;

m:array[1..100000] of longint;

procedure kbl(x:string);

var i,j,c,cl,a,b,d:longint;

y:string;

l:char;

begin

c:=length(x);

for i:=1 to c -1 do

for j:=i+1 to c do

if x<x[j] then begin

l:=x;

x:=x[j];

x[j]:=l;

end;

a:=0;b:=0;

for i:=1 to c do a:=a*10+ord(x)-48;

for i:=c downto 1 do b:=b*10+ord(x)-48;

d:=a-b;

i:=0;

while (m<>d)and(i<=cd) do inc(i);

if i>cd then begin

inc(cd);

m[cd]:=d;

y:='';

while d<>0 do begin

y:=y+chr((d mod 10)+48);

d:=d div 10;

end;

cl:=length(y);

if cl<c then for j:=cl to c-1 do y:=y+'0';

kbl(y);

end

else for j:=i to cd do write(m[j],' ');

end;

begin

readln(n);

gs:=0;

while n<>'' do begin

inc(gs);

s[gs]:=n;

readln(n);

end;

for t:=1 to gs do begin

cd:=1;

c2:=length(s[t]);

f:=0;

for r:=1 to c2 do f:=f*10+ord(s[t][r])-48;

m[cd]:=f;

kbl(s[t]);

writeln;

end;

end.

可计算多组数据至输入为空

[查看全文]
血のばら
学会像函数式编程者那样思考
展开Biu

让我们先来扯一下这样的一个话题,你是一个伐木工,在这森林中,你有着一把最好的斧头,这使得你成为了营地中最具生产效率的樵夫。后来有一天,有个家伙出现了,极力吹捧一种新的砍树工具的厉害之处,这种工具就是电锯。这卖东西的家伙很有说服力,因此你买了一把电锯,但你不知道它的工作方式。你试着举起它,并使用很大的力气在树上来回拉动,这就是你另外三种砍树工具的工作方式。你很快就断定这种新奇的电锯一类的玩意只不过是一种时髦的东西,于是你依旧使用斧头来把树砍倒。后来,有个家伙过来给你展示如何启动电锯。

你有可能就在这样的一个故事中出现过,不过是使用函数式编程(functional programming)代替了电锯。全新的编程范式的问题并不在于要学习一门新的语言,毕竟,语言的语法不过是细节,棘手的部分是要学会以一种不同的方式来思考。这就是我要开始的地方——电锯开动者和函数式编程者。

欢迎开始阅读函数式编程思想这一系列,该系列文章探讨了函数式编程这一主题,但内容并非是完全关于编程的语言的,而是正如我将要说明那样,以“函数”的方式来编写代码涉及了设计、权衡取舍、不同的可用构建块,以及在其他方面的许多领悟。我会尽可能用Java(或是类Java语言)来展示函数式编程的概念,并会转移到其他语言上说明还未在Java语言中存在的功能。我不会仓促行事,马上讨论诸如monad(参见资源一节)一类时髦的东西(尽管我们到时会涉及这部分内容),相反,我们会慢慢给你展示一种新的思考问题的方式(其实你已经在某些地方用到了这一方式——只是你没有意识到罢了)。

这一部分和接下来的三部分内容可看作是在与函数式编程相关的一些主题中的一个旋风之旅,主题中包括了一些核心的概念。随着我在整个系列中构建出越来越多的上下文背景和细微差别,其中的一些概念会以更加细节化的方式再次出现。作为这一旅程的出发点,我会让你看到问题两种不同实现,一种是命令式的编写方式,另一种带有更多函数式的倾向。

数字分类器


要谈论不同的编程风格的话,就需要比较代码。我们的第一个例子是在我的书The Productive Programmer(参加资源一节)中, 以及在“Test-driven Design, Part 1”和“Test-driven design, Part 2“(我之前的developerWorks系列Evolutionary architecture and emergent design中的两部分)这两篇文章中给出的一个编码问题的一个变体。我选择这一代码至少部分原因是因为这两篇文章深入地描述了代码的设计,这一文章所赞同的设计不存在什么问题,不过我在这里会提供一种不同的设计理念。

需求的陈述是这样的,给定任何大于1的正数,你需要把它归类为完美的(perfect)、富余的(abundant)或是欠缺的(deficient)。一个完美数字是一个这样的数值,它的因子(数字本身不能作为因子)相加起来等于该数值。类似地,一个富余数字的因子之和大于该数字,而一个欠缺数字的因子之和小于该数字。

命令式的数字分类器
清单1给出一个满足这些需求的命令式的类:

清单1. 数字分类器,问题的命令式解决方案
public class Classifier6 {

private Set _factors;

private int _number;

public Classifier6(int number) {

if (number < 1)

throw new InvalidNumberException(

"Can't classify negative numbers");

_number = number;

_factors = new HashSet>();

_factors.add(1);

_factors.add(_number);

}

private boolean isFactor(int factor) {

return _number % factor == 0;

}

public Set getFactors() {

return _factors;

}

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 int sumOfFactors() {

calculateFactors();

int sum = 0;

for (int i : _factors)

sum += i;

return sum;

}

public boolean isPerfect() {

return sumOfFactors() - _number == _number;

}

public boolean isAbundant() {

return sumOfFactors() - _number > _number;

}

public boolean isDeficient() {

return sumOfFactors() - _number < _number;

}

public static boolean isPerfect(int number) {

return new Classifier6(number).isPerfect();

}

}

代码中的几件事情是值得关注一下的:

1. 它有许多的单元测试(部分原因是因为我写这一代码的目的是用于测试驱动的开发的讨论)。

2. 该类包含了许多内聚的方法,这是在构建过程中使用测试驱动开发的一个边际效应。

3. calculateFactors() 方法中内嵌了性能上的优化。该类的重要部分包括了因子的收集,这样接下来我就可以合计它们,最终对它们分类。因子总是可以成对获得,例如,如果问题中的数字是16,则当我取得因子2时,我也可以取得8,因为2x8=16。如果我是以成对方式获得因子的话,那么我只需要以目标数字的平方根为上限值来检查因子就可以了。而这正是calculateFactors()方法采用的做法。

(稍微)函数式的分类器

使用同样的测试驱动的开发技术,我创建了分类器的另一个版本,清单2给出了该版本:

清单2. 稍微函数化一点的数字分类器
public class NumberClassifier {

static public boolean isFactor(int number, int potential_factor) {

return number % potential_factor == 0;

}

static public Set factors(int number) {

HashSet factors = new HashSet();

for (int i = 1; i <= sqrt(number); i++)

if (isFactor(number, i)) {

factors.add(i);

factors.add(number / i);

}

return factors;

}

static public int sum(Set factors) {

Iterator it = factors.iterator();

int sum = 0;

while (it.hasNext())

sum += (Integer) it.next();

return sum;

}

static public boolean isPerfect(int number) {

return sum(factors(number)) - number == number;

}

static public boolean isAbundant(int number) {

return sum(factors(number)) - number > number;

}

static public boolean isDeficient(int number) {

return sum(factors(number)) - number < number;

}

}

这两个分类器版本之间的差异很细微但很重要。主要的差别在于清单2中有针对性地省去了一些共享的状态。在函数式编程中,共享状态的消除(或至少是减少)是被青睐的抽象之一。不是使用跨方法的共享状态来作为中间结果(参见清单1中的factors域),我直接调用方法,省去了状态的使用。从设计的角度来看,这导致了factors()方法变长,但它防止了factors从方法中”泄漏出去“。还要注意的一点是,清单2可以全部由静态方法构成。方法之间不存在要共享的知识,所以我不太需要通过划定作用域来封装。如果你给这些方法提供它们所预期的输入参数的话,这些方法完全能工作得很好。(这是一个纯函数(pure function)的例子,在后面的部分中,我会对这一概念做进一步研究。)

函数

函数式编程是计算科学中一个涉及广泛、正四处扩展的领域,已经可见到最近对它的兴趣正在爆炸式地增长。JVM上的新的函数式语言(比如说Scala和Lojure),以及框架(比如说Functional Java和Akka)都已出现(参见资源一节),随之而来的通常是这样的断言:更少出错、更具生产效率、更好的外观、赚取更多的钱等等。我不打算把函数式编程的整个主题都拿出来分析处理,我会把重点放在几个主要的概念上,并关注一些派生自这些概念的有趣的实现。

函数式编程的核心是函数(function),就像类是面向对象语言中的主要抽象一样。函数形成了处理过程的构建块,满带着一些传统的命令式语言(imperative language)中没有的功能特性。

高阶函数
高阶函数(Higher-order function)可以把其他函数当成参数,也可以把其他函数作为结果返回。我们在Java语言中没有加入这一构造,最接近的做法是,你可以使用类(通常是匿名类)来作为你需要执行的方法的“持有者”。Java没有独立的函数(或方法),所以它们不能从函数中返回,或是作为参数传递。

在函数式语言中,这一功能很重要,原因至少有两个。首先,有高阶函数意味着你可以假设语言的各个部分以什么方式来互相配合。例如,你可以通过一种通用的机制来把某个类层次结构中的一些方法别类整个地去掉,该机制遍历列表并在每个元素上应用一个(或多个)高阶函数。(很快我就会给你展示一个这一构造的例子。)其次,通过启用函数来作为返回值,你就有机会构建出高动态、可自适应的系统。

但是,适合用高阶函数来解决的问题不仅只取决于函数式语言,在你以函数的方式来思考时,你解决问题的方法也会有所不同。考虑一下清单3中的例子(从一个较大的代码库中拿来的),一个对受保护的数据进行访问的方法:

清单3. 潜在可重用的代码模板
public void addOrderFrom(ShoppingCart cart, String userName,

Order order) throws Exception {

setupDataInfrastructure();

try {

add(order, userKeyBasedOn(userName));

addLineItemsFrom(cart, order.getOrderKey());

completeTransaction();

} catch (Exception condition) {

rollbackTransaction();

throw condition;

} finally {

cleanUp();

}

}

清单3中的代码完成初始化,执行某些工作,如果一切都顺利的话就完成事务,否则回滚,最后清空资源。显然,这段代码的样板部分可以重用,我们在面向对象的语言中通常是通过创建结构来实现这一点。在这一例子中,我结合了四人组设计模式(Gang of Four Design Patterns)(参见资源一节)中的两种模式:模板方法(Template Method)模式和命令(Command)模式。按照模板方法模式的建议,我应该把共同的样板代码往继承的层次结构的顶部移动,把算法的细节推迟到子类中实现。命令设计模式提供了一种使用公认的执行语义来把行为封装在一个类中的方式。清单4给出了把这两种模式应用到清单3中的代码上的结果:

清单4. 重构的订单代码
public void wrapInTransaction(Command c) throws Exception {

setupDataInfrastructure();

try {

c.execute();

completeTransaction();

} catch (Exception condition) {

rollbackTransaction();

throw condition;

} finally {

cleanUp();

}

}

public void addOrderFrom(final ShoppingCart cart, final String userName,

final Order order) throws Exception {

wrapInTransaction(new Command() {

public void execute() {

add(order, userKeyBasedOn(userName));

addLineItemsFrom(cart, order.getOrderKey());

}

});

}

在清单4中,我把代码中的通有部分提取出来放入wrapInTransaction()方法(其语义你应该认得——基本上是Spring的TransactionTemplate的一个简易版)中,把Command对象作为工作的单元传入。addOrderFrom()方法里折叠放置了一个匿名的内部类的定义,该类创建命令类,把两项工作包装了起来。

把所需的行为包装在一个命令类中纯粹是一种Java的设计工件,这其中不包含任何类型的独立行为,Java中的所有行为都必须驻留在类的内部。即使是语言的设计者也很快看出了这一设计中的缺陷——事后再想,认为不会存在不与类相关的行为的这种想法是有点天真。JDK 1.1通过加入匿名的内部类来纠正了这一缺陷,这至少是提供了一种语法糖,用于创建许多小的类,这些类只是有着些一些纯粹是功能性的而非结构性的方法。关于Java的这一方面,很有一些热衷于搞笑而又不乏幽默的文章,可以看一看Steve Yegge的“Execution in the Kingdom of Nouns”(参见资源一节)。

Java强制我创建了一个Command类的实例,即使我真正想要的不过是类中的方法而已。类本身不提供什么好处:其没有域、没有构造函数(除了Java自动生成的那个之外),也没有状态。其纯粹是充当方法内部的行为的包装器而已。在函数式语言中,这会通过高阶函数的处理加以代替。

如果愿意暂时把Java语言搁在一边的话,则我可以使用闭包(closure),闭包是一种接近函数式编程想法的语义。清单5给出了同样重构过的例子,不过使用的是Groovy(参见资源一节)而不是Java。

清单5. 使用Groovy闭包而不是命令类
def wrapInTransaction(command) {

setupDataInfrastructure()

try {

command()

completeTransaction()

} catch (Exception ex) {

rollbackTransaction()

throw ex

} finally {

cleanUp()

}

}

def addOrderFrom(cart, userName, order) {

wrapInTransaction {

add order, userKeyBasedOn(userName)

addLineItemsFrom cart, order.getOrderKey()

}

}

在Groovy中,花括号{}中的任何东西都是一个代码块,代码块可被当作参数传递,模仿高阶函数的功能。背地里,Groovy为你实现了命令设计模式。Groovy中的每个闭包块实际上是Goovy闭包类型的一个实例,其中包含了一个call()方法,当你在持有闭包实例的变量后面放置一对空的括号时,该方法就会被自动调用。Groovy启用了一些类函数式编程的行为,做法是其使用相应的语法糖来把适当的数据结构加入到语言自身中。正如我将要在接下来的各部分中展示的那样,Groovy还包含了其他的一些超越了Java的函数式编程功能。在系列的后面某个部分中,我还会回头再谈闭包和高阶函数之间的一些有趣的比较。

第一类函数
函数式语言中的函数被看作是第一类(first class)的,这意味着函数可以在出现在任何其他的语言构造(比如说变量)能够出现的地方。第一类函数的出现允许我们以非预期的方式来使用函数,并迫使我们以不同的方式来思考解决方法,比如说在普通的数据结构上采用相对通用的操作(有着稍有差别的细节)。而这相应地又暴露出了函数式语言思想方面的一个基本转变:注重结果而非步骤(focus on results, not steps)。

在命令式编程语言中,我必须考虑算法中的每一个原子步骤,清单1中的代码说明了这一点。为了解决数字分类器的问题,我必须要明确如何收集因子,而这相应地又意味着我不得不编写具体的代码来循环遍历数字来判断因子。但是循环遍历列表,在每个元素上进行操作,这听起来确实是一种常见的事情。考虑一下清单6中给出的、使用了Functional Java框架来重新实现的数字分类代码:

清单6. 函数式的数字分类器
public class FNumberClassifier {

public boolean isFactor(int number, int potential_factor) {

return number % potential_factor == 0;

}

public List factors(final int number) {

return range(1, number+1).filter(new F() {

public Boolean f(final Integer i) {

return number % i == 0;

}

});

}

public int sum(List factors) {

return factors.foldLeft(fj.function.Integers.add, 0);

}

public boolean isPerfect(int number) {

return sum(factors(number)) - number == number;

}

public boolean isAbundant(int number) {

return sum(factors(number)) - number > number;

}

public boolean isDeficiend(int number) {

return sum(factors(number)) - number < number;

}

}

清单6和清单2的主要区别在于两个方法:sum()和factors()。sum()方法利用了Functional Java中的List类的一个方法foldLeft(),这是被称作风化变质作用(catamorphism)的列表操纵概念的一种具体变化,这一概念指的是列表折叠的一种泛化。在这一例子中,“折叠剩余部分(fold left)”是指:

1. 获得一个初始值,并且通过在列表中的第一个元素上的操作来合并该值。

2. 获得结果,然后在下一个元素上采用相同的操作。

3. 继续进行这一操作直到走完列表。

可以注意到,这正是你在合计列表中的数字时要做的事情:从零开始,加上第一个元素,获得结果,接着把它和第二个元素相加,一直这样做直到列表中元素被用完。Functional Java提供高阶函数(在这一例子中是 Integers.add这一枚举)并且帮你很好地应用它。(当然,Java并不是真的有高阶函数,但是如果把它限定到某种具体的数据结构或是类型上的话,则你能够写一个很好的模拟体出来。)

清单6中另一个有些神秘的方法是factors(),该方法例证了我的“注重结果而非步骤”的建议。找出数字的因子的问题实质是什么?换一种表述方式,给定以一个目标数字为上限的所有可能数字的一个列表,如何确定哪些数字是该目标数字的因子?这暗示了一种过滤操作——我可以过滤整个列表中的数字,去掉不符合我的标准的那些。该方法读起来就是这样的一种描述:取得从1到我的数字的一个数字范围(范围是开区间的,因此+1);基于f()方法中的代码来过滤列表,这是Functional Java允许你使用具体的数据类型来创建类的方式;然后返回值。

作为编程语言的一个大趋势,这段代码还说明了一个更大的概念。在过去,开发者需要处理各种各样烦人的事情,比如说内存分配、垃圾收集以及指针等。随着时间的过去,久而久之,语言负责起了更多这方面的责任。随着计算机变得越来越强大,我们把越来越多的单调的(可自动化的)任务卸给了语言和运行时。作为一个Java开发者,我已经相当习惯于把所有的内存问题都丢给了语言。函数式语言扩充了这样的授权,包揽起更多具体的细节。随着时间的推移,我们会花费越来越少的时间来考虑需要用来解决问题的步骤,而会把越来越多地思考放在处理过程方面。随着这一文章系列的进展,我会给出许多这方面的例子。

结论

函数式编程更多的是一种思维模式,而不仅是工具或是语言的一个特殊集合。在文章系列的这第一部分中,我先论及一些函数式编程中的主题,从简单的设计决策到一些颇具挑战性的问题反思都有涵盖到。我重写了一个简单的Java类,以让它变得更函数化一些,然后开始进入一些主题,通过使用传统的命令式语言来突出函数式编程的不同。

这里先给出了两个很重要的、有长远影响的概念。第一个是,注重结果而非步骤。函数式编程尝试以不同的方式来表现问题,因为你用的是一些不同的构建块,这些构建块培植出了一些解决方案。我在整个系列中要说明的第二个趋势是,枯燥无味的的细节会被卸给编程语言和运行时,这就允许我们把重点放在编程问题的一些独特方面上。在系列的下一部分中,我会继续考虑函数式编程的一些常见方面的问题,以及研究如何把它应用到现时的开发上。

资源


学习资料 1. The Productive Programmer(Neal Ford,O'Reilly Media,2008):Neal Ford的最新著作进一步阐述了这一系列中的许多主题。

2. Monads:Monads是函数式编程语言中一个传奇式的颇为恐怖的主题,在这一系列的后续部分中会提及。

3. Scala:Scala是一种现代的、位于JVM之上的函数式语言。

4. Clojure:Clojure是一种现代的、运行在JVM上的函数式Lisp语言。

5. Podcast: Stuart Halloway on Clojure:更多地了解Clojure,关于为什么它会被迅速采用以及在普及率方面快速飙升,在这里你可以找出两个主要的原因。

6. Akka:Akka是一个Java框架,其允许复杂的基于参与者的并发。

7. Functional Java:Functional Java是一个框架,其为Java加入了许多的函数式语言构造。

8. “Execution in the Kingdom of Nouns”(Steve Yegge,March 2006):关于Java语言设计的某些方面的一些戏谑之言。

9. 浏览technology bookstore来查找一些关于这些和另外一些技术主题的书籍。

10. developerWorks Java technology zone:可以找到几百篇关于Java编程的各个方面的文章。

讨论
加入 developerWorks社区

关于作者

Neal Ford是一家全球性的IT咨询公司ThoughtWorks的软件架构师和Meme Wrangler。他还设计并编写了一些应用、教材、杂志文章、课件和视频/DVD演示,他是一些跨多种技术的书籍的作者或是编辑,其中包括了最近出版的这本The Productive Programmer。他的工作重点是设计和构建大型的企业级应用,他还是全球范围的开发者大会上的一位国际知名的演讲者。你可看看他的网站

该贴已经同步到 血のばら的微博

[查看全文]