九月 24, 2007

在Socket上使用了BeginReceive方法,需要手工处理超时问题.

(ReceiveTimeOut属性只在调用同步方法Receive()时才有效)

              IAsyncResult iar = handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new  AsyncCallback(OnReceive), state);
              if (!iar.AsyncWaitHandle.WaitOne(10000, false))
                   {
                                 String remoteIP = ((IPEndPoint)handler.RemoteEndPoint).Address.ToString();
                                String remotePort = ((IPEndPoint)handler.RemoteEndPoint).Port.ToString();
                                this.owner.DisconnectClient(remoteIP, remotePort);
                                handler.Close();
                                handler = null;
                   }                           
                                       

Powered by ScribeFire.

前一阵在一个project中使用了ReaderWriterLock,发现了两个问题:

  Performance非常差

 
 UpgradeToWriterLock并不是atomic的从ReaderLock转换到WriterLock,而是等同于
“lock.ReleaseReaderLock();
lock.AcquireWriterLock();”.这样的semantics有一定的迷惑性,我开始的时候也认为这个operation是
atomic的,等出现bug并debug了很久才发现原来如此。不过经过认真的思考,发现这其实不是。NET
designer的错,根本没办法把这个operation设计成atomic的。原因如下:

  很多个thread同时acquire到了ReaderLock,

  他们都call UpgradeToWriterLock,如果这个operation是atomic的,那么没有哪个thread能upgrade成功。

  后来我干脆不用ReaderWriterLock了,直接换成了LockFree的方法。这个得益于ibm的一些paper.(http://www.research.ibm.com/people/m/michael/pubs.htm

  在C#中实现LockFree其实是很简单的,因为有了Garbage Collection,

  code:

1 class LockFreeDictionary
2 {
3 private Dictionary m_dict = new Dictionary();
4
5 public Value Lookup(Key key)
6 {
7 return m_dict[key];
8 }
9
10 public void Update(Key key, Value value)
11 {
12 Dictionary newDict = null;
13 Dictionary oldDict = null;
14 do
15 {
16 oldDict = m_dict;
17 newDict = new Dictionary(oldDict);
18 newDict[key] = value;
19 } while (Interlocked.CompareExchange>(ref m_dict, newDict, oldDict) != oldDict);
20 }
21 }
22

  解释如下:

  line 16, keep a reference to the original Dictionary object,

  line 17, construct a new Dictionary object base on original object. For oldDict, this step is readonly, and doesn’t need lock,

  line 18, perform the update operation upon the new constructed object,

 
 line 19, try to swap the new object into the original one. If the
return value of Interlocked.CompareExchange operation is NOT equal to
oldDict, it means during this do-while block executation, there is
another thread changed m_dict. In this scenario, we need to do the
update again.

  the swapped out object (oldDict) can be collected by Garbage Collection.

 
 if we want to use LockFree data structure in C++, there is another
technique called Hazard Pointer. it’s in the ibm research papers.

  不过不是什么情况都可以使用这种LockFreeDictionary的,,不然你会得到相反的效果(performance很差),这里的scenario是read非常多,write非常少。 不过这种情况也挺常见的。

  这种方法的好处是在Lookup的时候没有任何lock,从而极大的提高了performance.(我的project里面比ReaderWriterLock提高了2000倍,)

  对LockFree有研究的或者有兴趣的可以留言大家讨论讨论,:)

Powered by ScribeFire.

九月 23, 2007

Lua语言简介

1993
年在巴西里约热内卢天主教大学(Pontifical Catholic University of Rio de Janeiro in
Brazil)诞生了一门编程语言,发明者是该校的三位研究人员,他们给这门语言取了个浪漫的名字——Lua,在葡萄牙语里代表美丽的月亮。事实证明她没
有糟蹋这个优美的单词,Lua语言正如它名字所预示的那样成长为一门简洁、优雅且富有乐趣的语言。

Lua从一开始就是作为一门方便嵌入
(其它应用程序)并可扩展的轻量级脚本语言来设计的,因此她一直遵从着简单、小巧、可移植、快速的原则,官方实现完全采用ANSI
C编写,能以C程序库的形式嵌入到宿主程序中。Lua的每个版本都保持着开放源码的传统,不过各版采用的许可协议并不相同,自5.0版(最新版是5.1)
开始她采用的是著名的MIT许可协议。正由于上述特点,所以Lua在游戏开发、机器人控制、分布式应用、图像处理、生物信息学等各种各样的领域中得到了越
来越广泛的应用。其中尤以游戏开发为最,许多著名的游戏,比如Escape from Monkey Island、World of
Warcraft、大话西游,都采用了Lua来配合引擎完成数据描述、配置管理和逻辑控制等任务。

作为一门过程型动态语言,Lua有着如
下的特性:1、变量名没有类型,值才有类型,变量名在运行时可与任何类型的值绑定;2、语言只提供唯一一种数据结构,称为表(table),它类似key
-value关联数组,可以用任何类型的值作为key和value。提供了一致且富有表达力的表构造语法,使得Lua很适合描述复杂的数据;3、函数是一
等类型,支持匿名函数和正则尾递归(proper tail recursion);4、支持词法定界(lexical
scoping)和闭包(closure);5、提供thread类型和结构化的协程(coroutine)机制,在此基础上可方便实现协作式多任务;
6、运行期能编译字符串形式的程序文本并载入虚拟机执行;7、通过元表(metatable)和元方法(metamethod)提供动态元机制
(dynamic
meta-mechanism),从而允许程序运行时根据需要改变或扩充语法设施的内定语义;8、能方便地利用表和动态元机制实现基于原型
(prototype-based)的面向对象模型;9、从5.1版开始提供了完善的模块机制,从而更好地支持开发大型的应用程序;

Lua
的语法类似PASCAL和Modula但更加简洁,所有的语法产生式规则(EBNF)不过才60几个。熟悉C和PASCAL的程序员一般只需半个小时便可
将其完全掌握。而在语义上Lua则与Scheme极为相似,她们完全共享上述的1、3、4、6点特性,
Scheme的continuation与协程也基本相同只是自由度更高。最引人注目的是,两种语言都只提供唯一一种数据结构:Lua的表和Scheme
的列表(list)。正因为如此,有人甚至称Lua为“只用表的Scheme”。

库和工具

相比Java、Python、Perl,Lua的开源工具和库可能并不算多,但其中不乏优秀之作。以下介绍的资源均可在http://lua-users.org/wiki/LuaAddons上找到,而且绝大多数都遵循着与Lua相同的许可协议。

一、Kepler

Kepler
是一个简单且轻量的Web开发平台(但这并不意味着只能用它来开发简单的应用),支持用Lua撰写Web程序,因此相当易学易用,并且能较方便地应用在一
些资源受限的系统中。由于使用ANSI C和Lua进行开发,所以它能移植到任何支持ANSI C的平台上。

Kepler由多个Lua扩
展库组成,包括CGILua、LuaSocket、LuaFileSystem、Copas、LuaSQL、LuaLDAP、LuaExpat、
LuaXMLRPC、LuaSOAP、LuaZip、Xavante等,它们可大致分为核心库和功能支撑库两部分。其中核心是CGILua和
LuaSocket,后者负责TCP/UDP
sockets的操作,前者则可以创建动态页面并处理web表单上的输入数据。Kepler通过CGILua起动器(launcher)使得Web服务器
能执行CGILua和Web程序并与之通信。目前的版本已经包括适合CGI、FastCGI、Apache、IIS、Tomcat、Zope的
CGILua起动器,因此用Lua开发的Web程序可以在这些种类的服务器中自由迁移,只要同时安装上对应的CGILua起动器。

LuaFileSystem
是对标准Lua库中文件读写功能的补充,它提供了一种可移植的方法来访问系统的目录结构和文件属性。Copas则是一个基于协程的服务调度器。
Xavante是一个用Lua开发的支持HTTP 1.1的Web服务器,它直接支持CGILua而无需起动器。

其它的组件提供了SQL数据库访问、XML解析、LDAP、SOAP、XMLRPC、ZIP文件操作等功能,用户如果只需要其中的某些功能,可以抽出相关组件(及其所依赖的组件)来使用。

二、wxLua

GUI
是开发人员花费气力比较大的一个领域,因此简化GUI程序的编写一直是广大程序员的努力方向。随着脚本语言的兴起,将动态、灵活、易用的脚本语言引入到
GUI开发中是一种非常有价值的尝试。由于复杂的GUI布局需要大量的描述信息,所以比起其它脚本来,既适合编程又适合描述数据的Lua语言在构建GUI
上就具有独特的优势。

wxWidgets是一个著名的跨平台C++
GUI库,wxLua在Lua与wxWidgets之间架起了一座桥梁,通过它Lua代码几乎可以调用wxWidgets的所有功能。wxLua基本将
wxWidgets的类体系映射到了Lua(基于原型)的对象模型中,这使得程序员能以基于对象或面向对象的风格来开发wxLua程序。一个Lua脚本的
撰写、运行、测试和修改可以非常快速,这无疑大大提高了GUI程序的开发效率,因此wxLua非常适合快速原型的构造。另外,Lua本身以及
wxWidgets良好的可移植性使得相应的Lua GUI程序在许多平台上都能顺畅地运行。

三、Pluto

虽然Lua
中的表能通过表构造器以Lua代码的形式保存到文件中从而实现持久化,但当数据之间有着复杂的引用关系,并且存在循环引用、共享引用等特殊情况时,这个任
务就变得相当困难与繁琐了。Pluto持久化库能够为用户解决这个难题。在它的帮助下程序员可以将任意复杂的表数据保存到特殊格式的二进制文件中以待将来
恢复,库会自动处理循环引用之类的情况。

除表之外,Pluto还支持函数(确切地说是闭包)、thread的持久化,这种能力非常有意
义。大家都知道程序调试中的一个基本动作就是复现bug,但很多时候bug产生的条件是非常复杂的,依赖很多因素,开发者很难精确地构建出完全一致的运行
环境。而利用Pluto对函数和thread的持久化能力,我们可以把bug发生时程序的完整运行环境保存下来,今后就可凭此方便地复现bug。另外一个
重要应用是游戏进度的保存。实现游戏逻辑的Lua脚本的运行状态能随时写入到文件中留待将来恢复,这使得在任何时间点保存游戏成了一件非常容易的事情。

四、LuaCOM

LuaCOM
是一个支持Lua与符合COM规范的组件对象(确切一点说是自动化对象)进行交互的扩展库。所谓交互包括了两个方面,首先是允许Lua程序使用COM对
象。LuaCOM支持注册在系统注册表中的COM对象的动态实例化,也支持动态访问运行中的对象。在LuaCOM的帮助下,调用COM对象方法就象调用普
通Lua函数一样,存取属性也与存取表的字段类似,同时它还负责Automation数据类型与Lua数据类型的自动转换。有了这些特性,Lua程序操作
COM对象就变得容易多了,再加上Lua天生的动态性,这无疑使其成了一门非常灵活的组件装配语言。

交互的另外一个方面就是支持用Lua
来实现(自动化)组件对象并提供给外部客户使用。LuaCOM同时支持进程内和进程外组件,它提供了一些辅助函数来处理注册、对象实例化这类事情,从而简
化了相关工作。由于LuaCOM实际上是根据Lua的表来构造一个COM对象,所以我们可以做一些非常有趣的事情:在userdata数据类型(代表不属
于Lua世界的数据结构)和动态元机制的支持下,Lua能通过表访问各种各样的外部数据,包括C++对象、C结构或者CORBA对象等等;LuaCOM可
以很方便地将代表这些数据的表包装成一个COM对象给外部使用,从而使得那些老迈的应用程序和库无需太多的努力便能跻身于COM世界。

五、tolua


接用C实现某些功能,然后将相应的函数导入到Lua中是很常见的做法。不过尽管Lua提供了与C语言交互的API,但用户必需手工进行繁琐的Lua栈(用
于与C交换数据)操作,而且还需注意两种语言数据类型的正确转换。难怪有人说使用Lua的C
API就象在使用汇编语言一样。为了减轻程序员的负担,一些C/C++ Wrapper应运而生。

tolua本身不是一个
Wrapper,但它是一个Wrapper代码自动生成器。它使用一种称为包(package)的文件来描述要导入到Lua环境中的常量、变量、函数、类
和方法,这种文件按照简化了的C++头文件格式编写。使用时首先让tolua解析包文件以生成含有相应胶水代码的C/C++源文件。然后将生成的源文件编
译并与那些具体实现功能的目标模块链接起来。

tolua虽然自动产生胶水代码,但需另外撰写描述文件,所以仍然不够方便。其它一些Wrapper库则利用C++模板元编程技术来自动生成合适的连接代码,从而避免了额外的描述文件,比如使用boost库的luabind。

六、LuaJIT

Lua
非常高效,它运行得比许多其它脚本(如Perl、Python、Ruby)都快,这点在第三方的独立测评中得到了证实。尽管如此,仍然会有人不满足,他们
总觉得“嗯,还不够快!”。LuaJIT就是一个为了再榨出一点速度的尝试,它利用JIT编译技术把Lua代码编译成本地机器码后交由CPU直接执行。
LuaJIT测评报告表明,在浮点运算、循环和协程的切换等方面它的加速效果比较显著,但如果程序大量依赖C编写的函数,那么运行速度便不会有什么改进。
目前LuaJIT只支持X86 CPU。

LuaJIT中包括一个名为Coco的库,用户可以单独使用它。Coco为C函数提供了真正的协
程能力,用户能在C函数内部的任何一点将协程挂起,然后在将来用协程恢复操作返回到那一点。在标准Lua中,协程的挂起与恢复是不允许跨越C函数调用边界
的。Coco使用了一些依赖于特定系统的特性,因此在移植程序时要特别注意。

七、ChunkSpy

Lua的虚拟机字节码指令集并非语言定义的一部分,因此官方没有提供相应的文档。用户当然可以通过查看相关源代码来获取信息,但这毕竟不方便。

ChunkSpy
是一个Lua虚拟机字节码反汇编器,可以将一个二进制Lua代码块输出为非常易读的各种格式(详细或简略,带或不带源程序)的字节码汇编文件。而且它还支
持交互式的反汇编,用户在敲入一行代码后立刻就能看到对应的字节码指令。ChunkSpy的作者写过一篇详细的介绍Lua5虚拟机指令的文章,名为《A
No-Frills Introduction to Lua 5 VM
Instructions》,你在项目主页上能找到它。这篇文章现在已经针对最新的Lua5.1做了更正。另外,他还是Yueliang项目的开发者,这
个项目采用Lua语言本身来实现Lua。从项目名来看,作者应该是个华人。

八、其它

另外一些库与工具还包括
LuaEDIT、LuaEclipse、VS’05LuaLangPack(它们都是IDE或IDE插件),LuaWrapper、CaLua、CPB、
CppLua(Wrapper库),LuaProfiler(性能测量工具)等,读者可以在lua-user.org与luaforge.net网站上找
到它们以及其它有用资源。

Powered by ScribeFire.

九月 22, 2007
EXIF,是英文Exchangeable Image
File(可交换图像文件)的缩写,最初由日本电子工业发展协会(JEIDA –Japan Electronic Industry Development
Association) 制订,目前的版本是修改发表于1998年6月的2.1版。国际标准化组织(ISO)正在制订的相机文件设计标准(DCF — Design
role for Camera File
system)就是以EXIF2.1格式为基础而设定的。记住,EXIF是一种图像文件格式,只是文件的后缀名还是沿用大家熟悉的jpg而已。实际上,EXIF信息就是由数码相机在拍摄过程中采集一系列的信息,然后把信息放置在我们熟知的jpg文件的头部,也就是说EXIF信息是镶嵌在JPEG图像文件格式内的一组拍摄参数,主要包括摄影时的光圈、快门、ISO、日期时间等各种与当时摄影条件相关的讯息,相机品牌型号,色彩编码,拍摄时录制的声音以及全球定位系统(GPS)等信息。简单的说,它就好像是傻瓜相机的日期打印功能一样,只不过EXIF信息所记录的资讯更为详尽和完备。不过,具有EXIF信息的JPEG图像文件要比普通的JPEG文件略大一点。就目前市场而言,新一代的数码相机都具有附加EXIF信息功能,大多数的数码相机厂商也都会随数码相机发售时附赠能够读取EXIF信息的软件,例如
Nikon 系列的数码相机就附赠 NikonView 软件,Agfa系列的相机则附赠 Photowize V1.8版,而富士相机附送的EXIF
viewer软件更是这方面的领军人物(目前已在很多网站提供免费下载。还有一部分的数码相机会自动将EXIF信息转存成文档文件,例如:NIKON CoolPix
990和SONY
FD系列。除了硬件厂商随数码相机附带的EXIF信息查看软件,很多专业的图像软件厂商在这方面也不甘示弱,相继推出自己公司看图软件的最新版来支持这种近乎完美的JPEG-EXIF图像信息附加技术,如最近刚推出的ACDSee
4.0版本,就对现在流行的各种数码相机有相当好的支持,在EXIF图像信息附加方面较之其3.0版本也有很大的进步。不管是硬件厂商的配套软件还是专业名门的看图工具,所有这些软件都是为了方便数码摄影者能更方便地保存查看摄影图像的重要信息。我们将这些读取EXIF信息的软件归纳后分为四类:专业EXIF信息查看工具(以富士的EXIF
viewer为例) 、具有查看EXIF信息的强大通用看图工具(以ACDSee为例)、支持EXIF信息查看的操作系统(微软的Windows
XP)以及可以修改EXIF信息的另类工具(EXIF
Editer),而我们这里要讲的是通过C#在WEB上读取一个图片的EXIF信息。

二、相关类(GetEXIFMetaData):

因为代码比较长,有五百多行,我只能将关键代码的片段拿出来讲讲,需要源码的请发邮件到 yubo@x263.net 索取,或者直接加到MSN上在MSN提供你的邮箱。

这个类中构造了两个结构MetadataDetail 和 Metadata  ,前者是为了存储EXIF中某元素信息的三种格式,起到中间转化的作用,一个是十六进制的索引信息,一个是没用处理过的信息代码,一个是显示信息。后面的那个结构就是存储一张图片所有EXIF信息元素的。 有两个方法LookupEXIFValue 和 GetEXIFMetaData ,前一个方法是处理特殊元素的对应显示的,后者从图片中读取相关信息然后填充到MetaData结构中。

using
System;
using System.Drawing;
using System.Drawing.Imaging;
using
System.Collections;
using System.ComponentModel;

namespace
Test.Com
{
/// <summary>
/// 功能:获得图片EXIF信息
/// 作者:Rexsp
///
创建日期:2004-03-20
/// </summary>
public class
EXIFMetaData
{
  #region 构造函数
  /// <summary>
  ///
构造函数
  /// </summary>
  public
EXIFMetaData()
  {
  }
  #endregion

  #region 数据转换结构
  ///
<summary>
  /// 转换数据结构
  /// </summary>
  public struct
MetadataDetail
  {
   public string Hex;//十六进制字符串
   public string
RawValueAsString;//原始值串
   public string
DisplayValue;//显示值串
  }
  #endregion

  #region EXIF元素结构
  ///
<summary>
  /// 结构:存储EXIF元素信息
  /// </summary>
  public
struct Metadata
  {
   public MetadataDetail EquipmentMake;
   public
MetadataDetail CameraModel;
   public MetadataDetail
ExposureTime;//曝光时间
   public MetadataDetail Fstop;
   public
MetadataDetail DatePictureTaken;
   public MetadataDetail ShutterSpeed;//
快门速度
   public MetadataDetail MeteringMode;//曝光模式
   public MetadataDetail
Flash;//闪光灯
   public MetadataDetail XResolution;
   public MetadataDetail
YResolution;
   public MetadataDetail ImageWidth;//照片宽度
   public
MetadataDetail ImageHeight;//照片高度

   public MetadataDetail
FNumber;//  f值,光圈数
   public MetadataDetail ExposureProg;//  曝光程序
  
public MetadataDetail SpectralSense;//  
   public MetadataDetail
ISOSpeed;//  ISO感光度
   public MetadataDetail OECF;//  
   public
MetadataDetail Ver;//  EXIF版本
   public MetadataDetail
CompConfig;//  色彩设置
   public MetadataDetail CompBPP;//  压缩比率
   public
MetadataDetail Aperture;//  光圈值
   public MetadataDetail
Brightness;//  亮度值Ev
   public MetadataDetail ExposureBias;//  曝光补偿
  
public MetadataDetail MaxAperture;//  最大光圈值

   public MetadataDetail
SubjectDist;// 主体距离
   public MetadataDetail LightSource;//  白平衡
   public
MetadataDetail FocalLength;//  焦距
   public MetadataDetail
FPXVer;//  FlashPix版本
   public MetadataDetail ColorSpace;//  色彩空间
  
public MetadataDetail Interop;//  
   public MetadataDetail
FlashEnergy;//  
   public MetadataDetail SpatialFR;//  
   public
MetadataDetail FocalXRes;//  
   public MetadataDetail FocalYRes;//  
  
public MetadataDetail FocalResUnit;//  
   public MetadataDetail
ExposureIndex;//  曝光指数
   public MetadataDetail SensingMethod;//  感应方式
  
public MetadataDetail SceneType;//  
   public MetadataDetail
CfaPattern;//  
  }
  #endregion

  #region 查找EXIF元素值
  public
string LookupEXIFValue(string Description, string Value)
  {
   string
DescriptionValue = null;

   switch(Description)
   {
    case
“MeteringMode”:

     #region  MeteringMode
    {
    
switch(Value)
     {
      case “0″:
       DescriptionValue =
“Unknown”;break;
      case “1″:
       DescriptionValue =
“Average”;break;
      case “2″:
       DescriptionValue = “Center
Weighted Average”;break;
      case “3″:
       DescriptionValue =
“Spot”;break;
      case “4″:
       DescriptionValue =
“Multi-spot”;break;
      case “5″:
       DescriptionValue =
“Multi-segment”;break;
      case “6″:
       DescriptionValue =
“Partial”;break;
      case “255″:
       DescriptionValue =
“Other”;break;
     }
    }
     #endregion
    
    
break;
    case “ResolutionUnit”:

     #region
ResolutionUnit
    {
     switch(Value)
     {
      case
“1″:
       DescriptionValue = “No Units”;break;
      case “2″:
      
DescriptionValue = “Inch”;break;
      case “3″:
       DescriptionValue =
“Centimeter”;break;
     }
    }

     #endregion

    
break;
    //省略N行相似代码

   }
   return
DescriptionValue;
  }
  #endregion

  #region
取得图片的EXIF信息
  public Metadata GetEXIFMetaData(string PhotoName)
  {
  
// 创建一个图片的实例
   System.Drawing.Image MyImage =
System.Drawing.Image.FromFile(PhotoName);
   // 创建一个整型数组来存储图像中属性数组的ID
  
int[] MyPropertyIdList = MyImage.PropertyIdList;
   //创建一个封闭图像属性数组的实例
  
PropertyItem[] MyPropertyItemList = new
PropertyItem[MyPropertyIdList.Length];
  
//创建一个图像EXIT信息的实例结构对象,并且赋初值

   #region 创建一个图像EXIT信息的实例结构对象,并且赋初值
  
Metadata MyMetadata = new Metadata();
   MyMetadata.EquipmentMake.Hex =
“10f”;
   MyMetadata.CameraModel.Hex = “110″;
  
MyMetadata.DatePictureTaken.Hex = “9003″;
   MyMetadata.ExposureTime.Hex =
“829a”;
   MyMetadata.Fstop.Hex = “829d”;
   MyMetadata.ShutterSpeed.Hex =
“9201″;
   MyMetadata.MeteringMode.Hex = “9207″;
   MyMetadata.Flash.Hex =
“9209″;
   MyMetadata.FNumber.Hex = “829d”; //
  
MyMetadata.ExposureProg.Hex = “”; //
   MyMetadata.SpectralSense.Hex =
“8824″; //
   MyMetadata.ISOSpeed.Hex = “8827″; //
  
MyMetadata.OECF.Hex = “8828″; //
   MyMetadata.Ver.Hex = “9000″; //
  
MyMetadata.CompConfig.Hex = “9101″; //
   MyMetadata.CompBPP.Hex = “9102″;
//
   MyMetadata.Aperture.Hex = “9202″; //
   MyMetadata.Brightness.Hex
= “9203″; //
   MyMetadata.ExposureBias.Hex = “9204″; //
  
MyMetadata.MaxAperture.Hex = “9205″; //
   MyMetadata.SubjectDist.Hex =
“9206″; //
   MyMetadata.LightSource.Hex = “9208″; //
  
MyMetadata.FocalLength.Hex = “920a”; //
   MyMetadata.FPXVer.Hex = “a000″;
//
   MyMetadata.ColorSpace.Hex = “a001″; //
   MyMetadata.FocalXRes.Hex
= “a20e”; //
   MyMetadata.FocalYRes.Hex = “a20f”; //
  
MyMetadata.FocalResUnit.Hex = “a210″; //
   MyMetadata.ExposureIndex.Hex =
“a215″; //
   MyMetadata.SensingMethod.Hex = “a217″; //
  
MyMetadata.SceneType.Hex = “a301″;
   MyMetadata.CfaPattern.Hex =
“a302″;
   #endregion

   // ASCII编码
   System.Text.ASCIIEncoding
Value = new System.Text.ASCIIEncoding();
            
   int index =
0;
   int MyPropertyIdListCount=MyPropertyIdList.Length;
  
if(MyPropertyIdListCount!=0)
   {
    foreach (int MyPropertyId in
MyPropertyIdList)
    {
     string hexVal = “”;
    
MyPropertyItemList[index] = MyImage.GetPropertyItem(MyPropertyId);

    
#region 初始化各属性值
     string
myPropertyIdString=MyImage.GetPropertyItem(MyPropertyId).Id.ToString(”x”);
    
switch(myPropertyIdString)
     {
      case “10f”:
      {
      
MyMetadata.EquipmentMake.RawValueAsString
=BitConverter.ToString(MyImage.GetPropertyItem (MyPropertyId).Value);
      
MyMetadata.EquipmentMake.DisplayValue =
Value.GetString(MyPropertyItemList[index].Value);
      
break;
      }

      case “110″:
      {
      
MyMetadata.CameraModel.RawValueAsString
=BitConverter.ToString(MyImage.GetPropertyItem(MyPropertyId).Value);
      
MyMetadata.CameraModel.DisplayValue
=Value.GetString(MyPropertyItemList[index].Value);
      
break;

      }

      case “9003″:
      {
      
MyMetadata.DatePictureTaken.RawValueAsString
=BitConverter.ToString(MyImage.GetPropertyItem(MyPropertyId).Value);
      
MyMetadata.DatePictureTaken.DisplayValue
=Value.GetString(MyPropertyItemList[index].Value);
      
break;
      }
    //省略N行相似代码
     }
     #endregion
    

     index++;
    }
   }

   MyMetadata.XResolution.DisplayValue
= MyImage.HorizontalResolution.ToString();
  
MyMetadata.YResolution.DisplayValue =
MyImage.VerticalResolution.ToString();
   MyMetadata.ImageHeight.DisplayValue
= MyImage.Height.ToString();
   MyMetadata.ImageWidth.DisplayValue =
MyImage.Width.ToString();
   MyImage.Dispose();
   return
MyMetadata;
  }
  #endregion
}
}

然后就是个调用的问题,有了这个类,我如何读取图片的EXIF信息呢?代码如下:
  
EXIFMetaData em = new EXIFMetaData();
   string
filePath=Server.MapPath(”Test.jpg”);//这里可以动态传递图片路径的
   EXIFMetaData.Metadata
m = em.GetEXIFMetaData(filePath);//这里就是调用,传图片绝对路径
   string exif =
m.Ver.DisplayValue;
   string camera = m.CameraModel.DisplayValue;
  
string model = m.CameraModel.DisplayValue;
   string aperture =
m.Aperture.DisplayValue;
   string shutter =
m.ShutterSpeed.DisplayValue;
   string sensitive =
m.ExposureIndex.DisplayValue;

有什么不明白的,欢迎提出来,此代码经过测试,功能正常。

Powered by ScribeFire.

九月 15, 2007

在参与了几个大型PHP项目,写了很多PHP代码以后,我发现很多工具可以提高代码质量,简化发布,使得做为PHP开发人员的生涯变得轻松许多。许多这样
的工具可能已经为大家所用。但是,由于一些人甚至没有注意到这些工具的存在,我会从此开始,好了,不说废话,下面是我认为所有PHP程序员应该知道的工具
列表。

Phing - 一个项目构建系统

Phing LogoPhing 是一个基于Apache ANT 的项目构建系统。它的名字是一个拗口的语句首字母缩写 - PHing Is Not GNU make 。Phing可以做传统构建系统比如 GNU make 能做的任何事情,同时没有陡峭的学习曲线。

在phing(以及其他的构建系统)背后的思想是评估一系列相关性,然后执行一系列PHP类去正确的安装和配置一个应用系统。构建过程用一个简单的
XML配置文件控制。Out of the box,
phing内置可以执行一系列替换(比如:在你的开发版本和产品版本间改变include路径)、执行SQL语句、移动和复制文件、运行外部脚本等等。通
过扩展包含在发行包中的“task”类,你也可以创造自己的定制任务。

对任何需要在超过一台服务器上部署大型PHP应用的人来世,Phing是一个无价之宝。同时我发现它对简单的PHP脚本也是很有用的。

Xdebug - 调试和分析工具

Xdebug LogoXdebug 是帮助你调试和分析脚本的一个PHP扩展。Xdebug中最有用的特性是在激活后显示的新注意信息、警告信息和错误信息。如果一个脚本不能正常运行,Xdebug将会输出关于错误信息的完整堆跟踪 ,会附带函数名称、参数值、源文件和行号。这对于厌倦了PHP默认只带的可怜的错误报告功能的程序员是一个值得欢迎的特性。

这个扩展有很多更先进的功能,允许开发人员进行代码覆盖率分析,收集分析信息以及交互式地调试脚本。profiling functionality 功能尤其有用。分析器使用一个普通的输出文件格式,允许你使用象 KCacheGrind 这样的工具快速发现你代码中的瓶颈。对任何严肃的开发人员而言,一个好的分析器是基本工具,它使得你妥善优化你的代码,同时避免过早优化带来的危害

PHPUnit - 单元测试框架

PHPUnit logoPHPUnit 是一个轻量级的PHP测试框架。它是在PHP5下面的 JUnit 3.8.1 完整移植,是xUnit 测试框架家族的一员(它们基于软件模式 先锋 Kent Beck 的设计)。

单元测试是几个现代敏捷开发方法的基础,使得PHPUnit成为许多大型PHP项目的关键工具。这个工具也可以被前面讨论的Xdebug扩展用来生成代码覆盖率报告 ,并且可以与phing集成来自动测试。

Propel - ORM(对象关系映射)框架

Propel LogoPropel是一个PHP5下面的对象关系映射(ORM)框架,它起源于Apache Torque项目。它提供了一个复杂但是易用的数据库抽象层,使得你可以在PHP中象使用普通类和对象一样得使用数据库实体。Propel允许你使用一种简单的XML格式定义你的数据库,这个XML文件被用来映射数据库,生成应用中使用的静态类。 

Propel被内嵌于流行的Symfony PHP框架 (还有其他的),它使得代码更灵活、模块化以及可移植。这个项目有The project has 出色的文档,以及很棒的支持社区。

phpMyAdmin / phpPgAdmin - 基于web的数据库管理系统

phpMyAdmin Logo历史有点长可是还是很有用,phpMyAdmin是对于任何数据库(对应PostgreSQL和SQLite是phpPgAdmin 和 phpSQLiteAdmin)最有用的管理工具之一。它有助于做每一件事情 - 从为了调试应用去创建和删除数据库到做备份。安装它通常是我在一个LAMP服务器上面安装完 Apache, PHP和MySQL后做的第一件事情。如果你使用MySQL,不知何故还没听说过它,那么现在就安装 。

其他工具

还有很多优秀的工具用以符合各种需要,帮助PHP开发者创建一个丰富的开发环境 — 我希望我可以说到所有这些工具。我发现对自己有用的工具有PHP Beautifier, Spyc, CreoleSmarty。我确信还有很多我忘记或者没听说过的有用工具。因此,如果你知道某个我遗漏的PHP开发伟大工具,请留言让我(以及所有人)知道!

Powered by ScribeFire.

九月 12, 2007

原文链接:Functional Programming For The Rest of Us
原文作者:Vyacheslav Akhmechet

程序员拖沓成性,每天到了办公室后,泡咖啡,检查邮箱,阅读 RSS
feed,到技术站点查阅最新的文章,在编程论坛的相关版面浏览公共讨论,并一次次地刷新以免漏掉一条信息。然后是午饭,回来后盯了IDE没几分钟,就再
次检查邮箱,倒咖啡。最后在不知不觉中,结束了一天。

不平凡的事是每隔一段时间会跳出一些很有挑战性的文章。如果没错,这些天你至少发现了一篇这类文章——很难快速通读它们,于是就将之束之高阁,直到
突然你发现自己已经有了一个长长的链接列表和一个装满了PDF文件的目录,然后你梦想着到一个人迹罕至的森林里的小木屋苦读一年以期赶上,要是每天清晨你
沿着那里的林中小溪散步时会有人带来食物和带走垃圾就更好了。

虽然我对你的列表一无所知,但我的列表却是一大堆关于函数式编程的文章。而这些基本上是最难阅读的了。它们用枯燥的学院派语言写成,即使“在华尔街
行业浸淫十年的专家(veterans)”也不能理解函数式编程(也写作FP)都在探讨些什么。如果你去问花旗集团(Citi
Group)或德意志银行(Deutsche Bank)的项目经理[1],为什么选择了 JMS 而不 Erlang,他们可能回答不能在产业级的应用中使用学院派语言。问题是,一些最为复杂的,有着最严格需求的系统却是用函数式编程元素写成。有些说法不能让人信服。

的确,关于函数式编程的文章和论文难于理解,但他们本来不必这么晦涩。这一知识隔阂的形成完全是历史原因。函数式编程的概念本身并不困难。这篇文章
可以作为“简易的函数式编程导引”。是一座从我们命令式(imperative)的思维模式到函数式编程的桥梁。去取杯咖啡回来继续读下去吧。可能你的同
事很快就会开始取笑你对函数式编程发表的观点了。

那么什么是函数式编程呢?它怎么产生?它可以被掌握吗(Is it
edible)?如果它真如其倡导者所言,为什么没有在行业中得到更广泛的使用?为什么好像只有那些拿着博士学位的人才使用它?最要紧的是,为什么它就
TMD 这么难学?这些 closure, continuation,
currying,惰性求值和无副作用等等究竟是些什么东西?没有大学参与的项目怎么使用它?为什么它看上去这么诡异于和我们命令式思想友好,圣洁和亲近
的一切的一切?我们将于不久扫清这些疑问。首先让我来解释形成实际生活和学界文章之间巨大隔阂的缘起,简单得像一次公园的散步。

信步游园

启动时间机器,我们散步在两千多年以前的一个被遗忘了太久的春季明媚的日子,那是公元前380年。雅典城墙外的橄榄树树荫里,柏拉图和一个英俊的奴隶小男孩朝着学院走去。“天气真好”,“饮食不错”,然后话题开始转向哲思。

“瞧那两个学生,”为了使问题更容易理解,柏拉图仔细地挑选着用词,“你认为谁更高呢?”
小男孩看着那两个人站着的水漕说,“他们差不多一样高”。
柏拉图说:“你的差不多一样是什么意思?”。“我在这里看他们是一样高的,不过我肯定如果走近些就会看出他们高度的差别。”
柏拉图笑了,他正把这个孩子带到正确的方向。“那么你是说,我们这个世界没有完全的等同了?”
小男孩想了一会儿回答,“对,我不这样认为,任何事物总有一些区别,即使我们看不到它。”
这句话非常到位!“那么如果这世上没有完全的相等,你又是如何理解‘完全’相等这个概念的呢?”
小男孩迷惑得说:“我不知道。”最初尝试着理解数学的本源(nature)时也会产生这种疑惑。

柏拉图暗示这个世上的万物都只是一个对完美的近似。他还认识到我们即使没有接触到完美但依然可以理解这一概念。所以他得出结论,完美的数学形式只能
存在于另一个世界,我们通过和那个世界的某种联系在一定程度上知晓他们。很明显我们不能看到完美的圆,但我们可以理解什么是完美的圆并用数学公式将它表达
出来。那么,什么是数学?为什么宇宙可以用数学定理描述?数学可以描述宇宙中的所有现象吗?[2]

数学哲学是一个很复杂的课题。像大多数哲学学科一样它更倾向于提出问题而不是给出解答。这些意见中很多都循回绕转于一个事实,即数学实际上是一个谜
语:我们设置了一系列基本的不冲突的原理和一些可以施加于这些原理的操作规则,然后我们就能堆砌这些规则以形成更复杂的规则。数学家把这种方法叫做“形式
系统”或“演算”。如果愿意,我们可以很快写出一个关于 Tetris(译者注:一种通常被称为俄罗斯方块的游戏)的形式系统。实际上,工作中的
Tetris 实现就是一个形式系统,只是被指定使用了个不常见的表现形式。

人马座的那个生物文明也许不能理解我们的 Tetris 和圆的范式,因为可能他们唯一的感知输入是气味香橙的橘子。他们也许永远不会发现
Tetris
范式,但很可能会有一个圆的范式。我们也可能将无法阅读它,因为我们的嗅觉没有那么复杂,可是一旦我们理解(pass)了那一范式的表示形式(通过这种传
感器和标准解码技术来理解这种语言),其底层的概念就可被任何智能文明所理解。

有趣的是如果从来没有智能文明存在,Tetris
和圆的范式仍然严密合理,只是没有人注定将会发现他们。如果产生了一种智能文明,他就会发现一些形式系统来帮助描述宇宙的规律。但他还是不大可能发现
Tetris 因为宇宙中再没有和它相似的事物。在现实世界中这类无用的形式系统或迷题的例子数不胜数,Tetris
只是其中的一个典型。我们甚至不能确定自然数是否是对客观世界的完整近似,至少我们可以简单的想像一个很大的数它不能用宇宙中任何东西描述,因为它以近乎
无穷。

历史一瞥[3]

再次启动时间机器,这一次的旅行近了很多,我们回到 1930 年代。大萧条正在蹂躏着那个或新或就的时代。空前的经济下挫影响着几乎所有阶层的家庭生活,只有少数人还能够保持着饥谨危机前的安逸。一些人就如此幸运地位列其中,我们关心的是普林斯顿大学的数学家们。

采用了歌特式风格设计建造的新办公室给普林斯顿罩上天堂般的幸福光环,来自世界各地的逻辑学家被邀请到普林斯顿建设一个新的学部。虽然彼时的美国民
众已难能弄到一餐的面包,普林斯顿的条件则是可以在高高的穹顶下,精致雕凿的木质墙饰边上整日的品茶讨论或款款慢步于楼外的林荫之中。

阿隆左·丘奇就是一个在这种近于奢侈的环境中生活着的数学家。他在普林斯顿获得本科学位后被邀留在研究生院继续攻读。阿隆左认为那里的建筑实属浮
华,所以他很少一边喝茶一边与人讨论数学,他也不喜欢到林中散步。阿隆左是一个孤独者:因为只有一个人时他才能以最高的效率工作。虽然如此,他仍与一些普
林斯顿人保持的定期的联系,其中包括阿兰·图灵,约翰·冯·诺依曼,和 kurt Grodel。

这四个人都对形式系统很感兴趣,而不太留意现实世界,以便致力于解决抽象的数学难题。他们的难题有些共同之处:都是探索关于计算的问题。如果我们有
了无限计算能力的机器,哪些问题可以被解决?我们可以使他们自动地得以解决吗?是否还是有些问题无法解决,为什么?不同设计的各种机器是否具有相同的计算
能力?

通过和其它人的合作,阿隆左·丘奇提出了一个被称为 lambda 演算的形式系统。这个系统本质上是一种虚拟的机器的编程语言,他的基础是一些以函数为参数和返回值的函数。函数用希腊字母 lambda 标识,这个形式系统因此得名[4]。利用这一形式系统,阿隆左就可以对上述诸多问题推理并给出结论性的答案。

独立于阿隆左,阿兰·图灵也在进行着相似的工作,他提出了一个不同的形式系统(现在被称为图灵机),并使用这一系统独立得给出了和阿隆左相似的结论。后来被证明图灵机和 lambda 演算能力等同。

我们的故事本可以到此结束,我会就此歇笔,而你也将浏览到下一个页面,如果第二次世界大战没有在那时打响。整个世界笼罩在战争的火光和硝烟之中,美
国陆军和海军前所未有的大量使用炮弹,为了改进炮弹的精确度,部队组织了大批的科学家持续地计算微分方程以解出弹道发射轨迹。渐渐意识到这个任务用人力手
工完成太耗精力后,人们开始着手开发各种设备来攻克这个难关。第一个解出了弹道轨迹的机器是 IBM 制造的 Mark I ——
它重达5吨,有75万个组件,每秒可以完成三次操作。

竞争当然没有就此结束,1949年,EDVAC(Electronic Discrete Variable Automatic
Computer,爱达瓦克)被推出并获得了极大的成功。这是对冯·诺依曼架构的第一个实践实例,实际上也是图灵机的第一个现实实现。那一年好运与阿隆左
·丘奇无缘。

直到1950年代将尽,一位 MIT 的教授John
McCarthy(也是普林斯顿毕业生)对阿隆左·丘奇的工作产生了兴趣。1958年,他公开了表处理语言 Lisp。Lisp 是对阿隆左·丘奇的
lambda 演算的实现但同时它工作在冯·诺依曼计算机上!很多计算机科学家认识到了 Lisp
的表达能力。1973年,MIT人工智能实验室的一组程序员开发了被称为Lisp机器的硬件-阿隆左 lambda 演算的硬件实现!

函数式编程

函数式编程是对阿隆左·丘奇理论的实践应用。但也并非全部 lambda 演算都被应用到了实践中,因为 lambda
演算不是被设计为在物理局限下工作的。因此,象面向对象的编程一样,函数式编程是一系列理念,而不是严格的教条。现在有很多种函数式编程语言,他们中的大
多数以不同方式完成不同任务。在本文中我将就最广泛使用的源自函数式编程的思想作一解释,并将用Java语言举例。(的确,你可以用Java写出函数式的
程序如果你有显著的受虐倾向)。在下面的小节中,我将会把Java作为一种函数式语言,并对其稍加修改使它成为一种可用的函数式语言。现在开始吧。

lambda
演算被设计用来探询关于计算的问题,所以函数式编程主要处理计算,并惊人地用函数来完成这一过程。函数是函数式编程的基本单位,函数几乎被用于一切,包括
最简单的计算,甚至变量都由计算取代。在函数式编程中,变量只是表达式的别名(这样我们就不必把所有东西打在一行里)。变量是不能更改的,所有变量只能被
赋值一次。用 Java 的术语来说,这意味着所有单一变量都被声明为 final(或 C++ 的 const)。在函数式编程中没有非 final
的变量。

final int i = 5;
final int j = i + 3;

因为函数式编程中所有变量都是 final 的,所以可以提出这样两个有趣的表述:没有必要总是写出关键字
final,没有必要把变量再称为变量。那么现在我们对Java作出两个修改:在我们的函数式 Java 中所有变量默认都是
final的,我们将变量(variable)称为符号(symbol)。

就此你也许会质疑,用我们新创造的语言还能写出有些复杂度的程序吗?如果每个符号都是不可变更(non-mutalbe)的,那么就无法改变任何状
态!其实事实并非完全如此。在阿隆左研究其 lambda
演算时,他并不想将某个状态维护一段时间以期未来对其进行修改。他关注的是对数据的操作(也通常被称为”演算体 caculating
stuff”)。既然已被证明lambda演算与图灵机等价,它可以完成所有命令式编程语言能够完成的任务。那么,我们怎么才能做到呢?

答案是函数式程序能保存状态,只是它并非通过变量而是使用函数来保存状态。状态保存在函数的参数中,保存在堆栈上。如果你要保存某个状态一段时间并
时不时地对其进行一些修改,可以写个递归函数。举个例子,我们写个函数来翻转 Java 的字符串。记住,我们声明的每个变量默认都是 final
的。[5]

String reverse(String arg) {
if(arg.length == 0) {
return arg;
}
else {
return reverse(arg.substring(1, arg.length)) + arg.substring(0,1);
}}

这个函数很慢因为它不断地调用自己[6],它还也是个嗜内存魔因为要持续分配对象。不过它的确是在用函数式风格。你可能会问,怎么有人会这样写程序?好的,我这就慢慢讲来:

函数式编程的优点

你可能会认为我根本无法对上面那个畸形的函数给出个合理的解释。我开始学习函数式编程时就是这么认为的。不过我是错了。有很好的理由使用这种风格,
当然其中一些属主观因素。例如,函数式程序被认为更容易阅读。因为每个街区的孩子都知道,是否容易理解在旁观者的眼中,所以我将略去这些主观方面的理由。
幸运的是,还有很多的客观理由。

单元测试

因为函数式编程的每一个符号都是 final 的,没有函数产生过副作用。因为从未在某个地方修改过值,也没有函数修改过在其作用域之外的量并被其他函数使用(如类成员或全局变量)。这意味着函数求值的结果只是其返回值,而惟一影响其返回值的就是函数的参数。

这是单元测试者的梦中仙境(wet
dream)。对被测试程序中的每个函数,你只需在意其参数,而不必考虑函数调用顺序,不用谨慎地设置外部状态。所有要做的就是传递代表了边际情况的参
数。如果程序中的每个函数都通过了单元测试,你就对这个软件的质量有了相当的自信。而命令式编程就不能这样乐观了,在 Java 或 C++
中只检查函数的返回值还不够——我们还必须验证这个函数可能修改了的外部状态。

调试

如果一个函数式程序不如你期望地运行,调试也是轻而易举。因为函数式程序的 bug
不依赖于执行前与其无关的代码路径,你遇到的问题就总是可以再现。在命令式程序中,bug
时隐时现,因为在那里函数的功能依赖与其他函数的副作用,你可能会在和 bug
的产生无关的方向探寻很久,毫无收获。函数式程序就不是这样——如果一个函数的结果是错误的,那么无论之前你还执行过什么,这个函数总是返回相同的错误结
果。

一旦你将那个问题再现出来,寻其根源将毫不费力,甚至会让你开心。中断那个程序的执行然后检查堆栈,和命令式编程一样,栈里每一次函数调用的参数都
呈现在你眼前。但是在命令式程序中只有这些参数还不够,函数还依赖于成员变量,全局变量和类的状态(这反过来也依赖着这许多情况)。函数式程序里函数只依
赖于它的参数,而那些信息就在你注视的目光下!还有,在命令式程序里,只检查一个函数的返回值不能够让你确信这个函数已经正常工作了,你还要去查看那个函
数作用域外数十个对象的状态来确认。对函数式程序,你要做的所有事就是查看其返回值!

沿着堆栈检查函数的参数和返回值,只要发现一个不尽合理的结果就进入那个函数然后一步步跟踪下去,重复这一个过程,直到它让你发现了 bug 的生成点。

并行

函数式程序无需任何修改即可并行执行。不用担心死锁和临界区,因为你从未用锁!函数式程序里没有任何数据被同一线程修改两次,更不用说两个不同的线程了。这意味着可以不假思索地简单增加线程而不会引发折磨着并行应用程序的传统问题。

事实既然如此,为什么并不是所有人都在需要高度并行作业的应用中采用函数式程序?嗯,他们正在这样做。爱立信公司设计了一种叫作 Erlang
的函数式语言并将它使用在需要极高抗错性和可扩展性的电信交换机上。还有很多人也发现了 Erlang
的优势并开始使用它。我们谈论的是电信通信控制系统,这与设计华尔街的典型系统相比对可靠性和可升级性要求高了得多。实际上,Erlang
系统并不可靠和易扩展,Java 才是。Erlang 系统只是坚如磐石。

关于并行的故事还没有就此停止,即使你的程序本身就是单线程的,那么函数式程序的编译器仍然可以优化它使其运行于多个CPU上。请看下面这段代码:

String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);

在函数编程语言中,编译器会分析代码,辨认出潜在耗时的创建字符串s1和s2的函数,然后并行地运行它们。这在命令式语言中是不可能的,因为在那
里,每个函数都有可能修改了函数作用域以外的状态并且其后续的函数又会依赖这些修改。在函数式语言里,自动分析函数并找出适合并行执行的候选函数简单的像
自动进行的函数内联化!在这个意义上,函数式风格的程序是“不会过时的技术(future
proof)”(即使不喜欢用行业术语,但这回要破例一次)。硬件厂商已经无法让CPU运行得更快了,于是他们增加了处理器核心的速度并因并行而获得了四
倍的速度提升。当然他们也顺便忘记提及我们的多花的钱只是用在了解决平行问题的软件上了。一小部分的命令式软件和 100%
的函数式软件都可以直接并行运行于这些机器上。

代码热部署

过去要在 Windows上安装更新,重启计算机是难免的,而且还不只一次,即使是安装了一个新版的媒体播放器。Windows XP
大大改进了这一状态,但仍不理想(我今天工作时运行了Windows
Update,现在一个烦人的图标总是显示在托盘里除非我重启一次机器)。Unix系统一直以来以更好的模式运行,安装更新时只需停止系统相关的组件,而
不是整个操作系统。即使如此,对一个大规模的服务器应用这还是不能令人满意的。电信系统必须100%的时间运行,因为如果在系统更新时紧急拨号失效,就可
能造成生命的损失。华尔街的公司也没有理由必须在周末停止服务以安装更新。

理想的情况是完全不停止系统任何组件来更新相关的代码。在命令式的世界里这是不可能的。考虑运行时上载一个Java类并重载一个新的定义,那么所有
这个类的实例都将不可用,因为它们被保存的状态丢失了。我们可以着手写些繁琐的版本控制代码来解决这个问题,然后将这个类的所有实例序列化,再销毁这些实
例,继而用这个类新的定义来重新创建这些实例,然后载入先前被序列化的数据并希望载入代码可以恰到地将这些数据移植到新的实例。在此之上,每次更新都要重
新手动编写这些用来移植的代码,而且要相当谨慎地防止破坏对象间的相互关系。理论简单,但实践可不容易。

对函数式的程序,所有的状态即传递给函数的参数都被保存在了堆栈上,这使的热部署轻而易举!实际上,所有我们需要做的就是对工作中的代码和新版本的
代码做一个差异比较,然后部署新代码。其他的工作将由一个语言工具自动完成!如果你认为这是个科幻故事,请再思考一下。多年来
Erlang工程师一直更新着他们的运转着的系统,而无需中断它。

机器辅助的推理和优化

函数式语言的一个有趣的属性就是他们可以用数学方式推理。因为一种函数式语言只是一个形式系统的实现,所有在纸上完成的运算都可以应用于用这种语言书写的程序。编译器可以用数学理论将转换一段代码转换为等价的但却更高效的代码[7]。多年来关系数据库一直在进行着这类优化。没有理由不能把这一技术应用到常规软件上。

另外,还能使用这些技术来证明部分程序的正确,甚至可能创建工具来分析代码并为单元测试自动生成边界用例!对稳固的系统这种功能没有价值,但如果你
要设计心房脉冲产生器 (pace
maker)或空中交通控制系统,这种工具就不可或缺。如果你编写的应用程序不是产业的核心任务,这类工具也是你强于竞争对手的杀手锏。

高阶函数

我记得自己在了解了上面列出的种种优点后曾想:“那都是非常好的特性,可是如果我不得不用天生就不健全的语言编程,把一切变量声明为
final 产生的代码将是垃圾一堆。” 这其实是误解。在如Java 这般的命令式语言环境里,将所有变量声明为 final 没有用,但是在函数式语言里不是这样。函数式语言提供了不同的抽象工具它会使你忘记你曾经习惯于修改变量。高阶函数就是这样一种工具。

函数式语言中的函数不同于 Java 或 C 中的函数,而是一个超集——它有着 Java 函数拥有的所有功能,但还有更多。创建函数的方式和 C 中相似:

int add(int i, int j) {
return i + j;
}

这意味着有些东西和同样的 C 代码有区别。现在扩展我们的 Java 编译器使其支持这种记法。当我们输入上述代码后编译器会把它转换成下面的Java代码(别忘了,所有东西都是 final 的):

class add_function_t {
int add(int i, int j) {
return i + j;
}
}

add_function_t add = new add_function_t();

这里的符号 add 并不是一个函数。这是一个有一个成员函数的很小的类。我们现在可以把 add
作为函数参数放入我们的代码中。还可以把它赋给另一个符号。我们在运行时创建的 add_function_t
的实例如果不再被使用就将会被垃圾回收掉。这些使得函数成为第一级的对象无异于整数或字符串。(作为参数)操作函数的函数被称为高阶函数。别让这个术语吓
着你,这和 Java 的 class 操作其它
class(把它们作为参数)没有什么区别。我们本可以把它们称为“高阶类”但没有人注意到这个,因为 Java 背后没有一个强大的学术社区。

那么怎样,何时应该使用高阶函数呢?我很高兴你这样问。如果你不曾考虑类的层次,就可能写出了一整团堆砌的代码块。当你发现其中一些行的代码重复出
现,就把他们提取成函数(幸运的是这些依然可以在学校里学到)。如果你发现在那个函数里一些逻辑动作根据情况有变,就把他提取成高阶函数。糊涂了?下面是
一个来自我工作的实例:假如我的一些 Java 代码接受一条信息,用多种方式处理它然后转发到其他服务器。

class MessageHandler {
void handleMessage(Message msg) {
// …
msg.setClientCode(”ABCD_123″);
// …

sendMessage(msg);
}

// …

}

现在假设要更改这个系统,现在我们要把信息转发到两个服务器而不是一个。除了客户端的代码一切都像刚才一样——第二个服务器希望这是另一种格式。怎么处理这种情况?我们可以检查信息的目的地并相应修改客户端代码的格式,如下:

class MessageHandler {
void handleMessage(Message msg) {
// …
if(msg.getDestination().equals(”server1″) {
msg.setClientCode(”ABCD_123″);
} else {
msg.setClientCode(”123_ABC”);
}
// …

sendMessage(msg);
}

// …

}

然而这不是可扩展的方法,如果加入了更多的服务器,这个函数将线性增长,更新它会成为我的梦魇。面向对象的方法是把MessageHandler作为基类,在导出类中专业化客户代码操作:

abstract class MessageHandler {
void handleMessage(Message msg) {
// …
msg.setClientCode(getClientCode());
// …

sendMessage(msg);
}

abstract String getClientCode();

// …

}

class MessageHandlerOne extends MessageHandler {
String getClientCode() {
return “ABCD_123″;
}

}

class MessageHandlerTwo extends MessageHandler {
String getClientCode() {
return “123_ABCD”;
}

}

现在就可以对每一个服务器实例化一个适合的类。添加服务器的操作变得容易维护了。但对于这么一个简单的修改仍然要添加大量的代码。为了支持不同的客户代码我们创建了两个新的类型!现在我们用高阶函数完成同样的功能:

class MessageHandler {
void handleMessage(Message msg, Function getClientCode) {
// …
Message msg1 = msg.setClientCode(getClientCode());
// …

sendMessage(msg1);
}

// …

}

String getClientCodeOne() {
return “ABCD_123″;

}

String getClientCodeTwo() {
return “123_ABCD”;

}

MessageHandler handler = new MessageHandler();
handler.handleMessage(someMsg, getClientCodeOne);

没有创建新的类型和新的class层次,只是传入合适的函数作为参数,完成了面向对象方式同样的功能,同时还有一些额外的优点。没有使自己囿于类的
层次之中:可以在运行时传入函数并在任何时候以更高的粒度更少的代码修改他们。编译器高效地为我们生成了面向对象的“粘合”代码!除此之外,我们还获得了
所有函数式编程的其他好处。当然函数式语言提供的抽象不只这些,高阶函数只是一个开始:

currying

我认识的大多数人都读过“四人帮”的那本设计模式,任何自重的程序员都会告诉你那本书是语言中立的(agnostic),模式在软件工程中是通用的,和使用的语言无关。这个说法颇为高贵,故而不幸的是,有违现实。

函数式编程极具表达能力。在函数式语言中,语言既已达此高度,设计模式就不再是必需,最终你将设计模式彻底消除而以概念编程。适配器
(Adapter)模式就是这样的一个例子。(究竟适配器和 Facade 模式区别在哪里?可能有些人需要在这里再多费些篇章)。一旦语言有了叫作
currying 的技术,这一模式就可以被消除。

currying.

适配器模式最有名的是被应用在 Java 的“默认”抽象单元——class 上。在函数式编程里,模式被应用到函数。模式带有一个接口并将它转换成另一个对他人有用的接口。这有一个适配器模式的例子:

int pow(int i, int j);
int square(int i)
{
return pow(i, 2);
}

上面的代码把一个整数幂运算接口转换成为了一个平方接口。在学术文章里,这个雕虫小技被叫作currying(得名于逻辑学家Haskell
Curry,他曾将相关的数学理论形式化 )。因为在函数式编程中函数(反之如class)被作为参数来回传递,currying 很频繁地被用来把函数调整为更适宜的接口。因为函数的接口是他的参数,使用 currying 可以减少参数的数目(如上例所示)。

函数式语言内建了这一技术。不用手动地创建一个包装了原函数的函数,函数式语言可以为你代劳。同样地,扩展我们的语言,让他支持这个技术:

square = int pow(int i, 2);

这将为我们自动创建出一个有一个参数的函数 square。他把第二个参数设置为 2 再调用函数 pow。这行代码会被编译为如下的 Java 代码:

class square_function_t {
int square(int i) {
return pow(i, 2);
}
}

square_function_t square = new square_function_t();

正如你所见,通过简单地创建一个对原函数的包装,在函数式编程中,这就是 currying —— 快速简易创建包装的捷径。把精力集中在你的业务上,让编译器为你写出必要的代码!什么时候使用 currying?这很简单,任何时候你想要使用适配器模式(包装)时。

惰性求值

惰性(或延迟)求值这一技术可能会变得非常有趣一旦我们采纳了函数式哲学。在讨论并行时已经见过下面的代码片断:

String s1 = somewhatLongOperation1();
String s2 = somewhatLongOperation2();
String s3 = concatenate(s1, s2);

在一个命令式语言中求值顺序是确定的,因为每个函数都有可能会变更或依赖于外部状态,所以就必须有序的执行这些函数:首先是
somewhatLongOperation1,然后 somewhatLongOperation2,最后 concatenate,在函数式语言里就不尽然了。

前面提到只要确保没有函数修改或依赖于全局变量,somewhatLongOperation1 和
somewhatLongOperation2
可以被并行执行。但是如果我们不想同时运行这两个函数,还有必要保证有序的执行他们呢?答案是不。我们只在其他函数依赖于s1和s2时才需要执行这两个函
数。我们甚至在concatenate调用之前都不必执行他们——可以把他们的求值延迟到concatenate函数内实际用到他们的位置。如果用一个带
有条件分支的函数替换concatenate并且只用了两个参数中的一个,另一个参数就永远没有必要被求值。在 Haskell
语言中,不确保一切都(完全)按顺序执行,因为 Haskell 只在必要时才会对其求值。

惰性求值优点众多,但缺点也不少。我们会在这里讨论它的优点而在下一节中解释其缺点。

优化

惰性求值有客观的优化潜力。惰性编译器看函数式代码就像数学家面对的代数表达式————可以注销一部分而完全不去运行它,重新调整代码段以求更高的
效率,甚至重整代码以降低出错,所有确定性优化(guaranteeing
optimizations)不会破坏代码。这是严格用形式原语描述程序的巨大优势————代码固守着数学定律并可以数学的方式进行推理。

抽象控制结构

惰性求值提供了更高一级的抽象,它使得不可能的事情得以实现。例如,考虑实现如下的控制结构:

unless(stock.isEuropean()) {
sendToSEC(stock);
}

我们希望只在祖先不是欧洲人时才执行sendToSEC。如何实现 unless?如果没有惰性求值,我们需要某种形式的宏(macro)系统,但
Haskell 这样的语言不需要它。把他实现为一个函数即可:

void unless(boolean condition, List code) {
if(!condition)
code;
}

注意如果条件为真代码将不被执行。我们不能在一个严格(strict)的语言中再现这种求值,因为 unless 调用之前会先对参数进行求值。

无穷(infinite)数据结构

惰性求值允许定义无穷数据结构,对严格语言来说实现这个要复杂的多。考虑一个 Fibonacci
数列,显然我们无法在有限的时间内计算出或在有限的内存里保存一个无穷列表。在严格语言如 Java 中,只能定义一个能返回 Fibonacci
数列中特定成员的 Fibonacci 函数,在 Haskell
中,我们对其进一步抽象并定义一个关于 Fibonacci 数的无穷列表,因为作为一个惰性的语言,只有列表中实际被用到的部分才会被求值。这使得可以抽象出很多问题并从一个更高的层次重新审视他们。(例如,我们可以在一个无穷列表上使用表处理函数)。

缺点

当然从来不存在免费的午餐。惰性求值有很多的缺点,主要就在于,懒。有很多现实世界的问题需要严格求值。例如考虑下例:

System.out.println(”Please enter your name: “);
System.in.readLine();

在惰性求值的语言里,不能保证第一行会在第二行之前执行!那么我们就不能进行输入输出操作,不能有意义地使用本地(native)接口(因为他们相
互依赖其副作用必须被有序的调用),从而与整个世界隔离。如果引入允许特定执行顺序的原语又将失去数学地推理代码的诸多好处(为此将葬送函数式编程与其相
关的所有优点)。幸运的是,并非丧失了一切,数学家为此探索并开发出了许多技巧来保证在一定函数设置下(function
setting)代码以一特定的顺序执行。这样我们就赢得了两个世界。这些技术包括 continuation, monad 和
uniqueness typing
(一致型别)。我只会在本文中解释continuation,把 monad 和 uniqueness typing 留到将来的文章中。有趣的是,除了确保函数求值顺序, continuation 在很多别的情况下也很有用。这点等一会儿就会提到。

Continuations

Continuations 对于程序设计的意义,就像《达芬奇密码》对人类历史的意义:即对人类最大秘密的惊人揭示。也许不是,但他在概念上的突破性至少和揭示了负数的平方根意义等同。

我们在学习函数时,只是学到了一半的事实,因为我们基于一个错误的假定:函数只能将结果返回到它的调用函数。在这个意思上continuation
是广义的函数。函数不必要返回到其调用函数而可以返回到程序的任何地方。我们把”continuation”
作为参数传给一个函数,它指定了这个函数返回的位置。这个描述可能听起来更加复杂。看一下下面的代码:

int i = add(5, 10);
int j = square(i);

函数 add 在其被调用的位置将结果 15 赋给了 i,接下来 i 的值被用来调用
square。注意所有的惰性求值编译器都不能调整这几行代码因为第二行依赖着第一行的成功求值。下面用 continuation 风格又称 CPS
(Continuation Programming Style) 来重写这段代码,这里函数 add 会将结果返回到 square
而不是原来的调用函数。

int j = add(5, 10, square);

这个例子中 add 有了另一个参数 —— 一个 add 必须在它求值结束时用其返回值调用的函数。这里 square 是 add 的一个 continuation。这两种情况下,j 都将等于 255。

这就是强制使惰性语言有序地求值两个表达式的第一个技巧。考虑下面这个(熟悉的)IO代码:

System.out.println(”Please enter your name: “);
System.in.readLine();

这两行不相依赖所以编译器会自由的重新调整他们的执行顺序。然而,如果我们用 CPS 来重写这段代码,就会有一个依赖,编译器会因此而强制对这两行代码有序执行!

System.out.println(”Please enter your name: “, System.in.readLine);

这里 println 需要用自己的返回结果作为参数去调用 readLine 并将 readLine
返回值作为自己的返回值。这样就能确保这两行被有序执行而且 readLine 一定被执行(因为整个计算期望最后的结果为结果)。Java 的
println 返回 void
但如果它返回的是一个抽象值(readLine所期待的),我们就解决了这个问题!当然这样的链接函数调用很快就会使代码难以读懂,不过这个可以避免。比
如我们可以给语言添加些语法甜点(syntactic
sugar)就可以简单的按顺序输入表达式,然后由编译器自动为我们链接这些函数调用。这样就可以如愿地使用期望的求值顺序并保留一切函数式编程的好处
(包括数学地对我们程序进行推理的能力)!如果还是有迷惑,记住函数是只有一个成员的类的实例。重写上述代码使得 println 和
readLine 成为类的实例,这样就对一切都清楚了。

如果我在此结束本节,那将仅仅涉及到 continuation 最浅显的应用。用 CPS 重写整个程序,那里所有的函数都增加一个额外的
continuation 参数并把函数结果传给它。也可以通过简单地把函数当作 continuation
函数(总是返回到调用者的函数)的特殊实例来将程序转为 CPS 风格。这种转换很容易被自动化(事实上,许多编译器就是这么做的)。

一旦我们将一个程序转为了CPS,那么很明显每个指令都将有些 continuation,
这是一个该指令在执行结束时会用其执行结果调用的函数,通常的程序中,这是一个它要返回的地址。从上面的例子中随便举个例子,比如 add(5,
10)。在用CPS风格写的程序里,add 的continuation很明显——这是一个 add
在其执行结束时会调用的函数。那么如果在非CPS的程序里,它是什么呢?当然我们可以把程序转为 CPS ,但有这个必要吗?

其实没有必要。仔细看一下我们的 CPS 转换过程。如果尝试为它写一个编译器,然后经过长期的思考后,你意识到这个 CPS
的版本根本不需要栈!没有函数会以传统的意义“返回”,它只是用结果调用了另一个函数。我们无需在调用时将函数参数压栈再于调用结束时弹出栈,而只是简单
的把他们保存在一大块内存中,然后使用跳转指令。不再需要原来的参数——他们不会再次被用到,因为没有函数会返回!

所以,用 CPS 风格写成的程序没有堆栈,但每个函数却有一个额外的参数可被调用。不是 CPS
风格的程序没有可以被调用的这个参数,但却有栈。栈中存放着什么?只是参数和一个指向函数返回地址的指针。你看到光了吗?栈中只是放着
continuation 的信息! 栈中指向返回指令的指针本质上和 CPS 程序里将被调用的函数是等价的。如果你想探究 add(5,10) 的
continuation,只要简单地检查它在堆栈的执行点!

这的确很简单。continuation 和栈上指向返回地址的指针是等价的,只是 continuation
是被显式传递,所以不必和函数被调用点是同一位置。如果还记得 continuation
就是一个函数,并且在我们的语言里,函数被编译为一个类的实例,你就会理解指向栈中返回指令的指针实际就是传递给 continuation
的参数,因为我们的函数(就像一个类的实例)只是一个指针。这意味着给定程序中任意时间和任意位置,你都可以去请求一个当前的
continuation (current continuation)(它就是当前的栈的信息)。

好的,这样我们就知道了什么是 current continuation。他有什么意义?一旦我们得到了当前的 continuation
并将它保存在某处,我们就最终将程序当前的状态保存了下来——及时地冷冻下来。这就像操作系统将其置为休眠状态。一个 continuation
对象里保存了在我们获得它的地方重新启动程序的必要信息。操作系统在每次发生线程间的上下文切换时也是如此。唯一的区别是它保留着全部控制。请求一个
continuation 对象(在Scheme里,可以调用 call-with-current-continuation
函数)后,你就会获得一个包括了当前 continuation
的对象——堆栈(或者在CPS情况下则是下一个要调用的函数)。可以把这个对象保存在一个变量(或者是磁盘)里。当你用这 continuation
“重启”程序时,就会转回到处你取得这个对象的那个状态。这就象切换回一个被挂起的线程或唤醒休眠着的操作系统,区别是用
continuation,你可以多次地重复这一过程。当操作系统被唤醒时,休眠信息就被销毁了。但如果那些信息没有被销毁,你也就可以一次次地将它唤醒
到同一点,就象重返过去一样。有了 continuation 你就有了这个控制力!

Continuation 应该在什么情况下使用呢?通常在尝试模拟一个本质上是无状态的应用时可以简化你的任务。Continuation
很适合在Web应用程序中使用。微软公司的 ASP.NET 技术极尽苦心地模拟状态以便你在开发 Web 应用时少费周折。可如果 C#
支持了continuation,ASP.NET 的复杂度就可以减半——你只需要保存一个 continuation,当用户下次发出 web
请求时重启它即可。对程序员来说,web 应用程序将不再有中断——程序只是简单的从下一行重启!利用 continuation
这一抽象解决问题真是令人难以置信的便利。考虑到越来越多的胖客户端应用程序正在向服务器端转移,将来 continuation 也会变得越来越重要。

模式匹配

模式匹配不是什么新的创新的特性。事实上,它和函数式编程的关系不大。把产生模式匹配归因于函数式编程的唯一的原因是函数式语言一度提供了模式匹配,然而现在的命令式语言还做不到。

让我们用一个例子深入了解一下模式匹配。这是一个Java的Fibonacci函数:

int fib(int n) {
if(n == 0) return 1;
if(n == 1) return 1;

return fib(n - 2) + fib(n - 1);
}

让我们从Java衍生出的语言来支持模式匹配:

int fib(0) {
return 1;
}

int fib(1) {
return 1;
}

int fib(int n) {
return fib(n - 2) + fib(n - 1);

}

两者有什么区别?编译器为我们实现了分支。这有什么大不了?的确没什么。有人注意到很多函数包括了复杂的 swith
语句(尤其是在函数式程序中)所以认为这种抽象形式很好。我们把一个函数定义分离成多个,然后把模式置于参数中(有点象重载)。当这个函数被调用时,编译
器使其比较参数和其运行时的定义然后选择其中正确的一个。这一般是通过选择可选的最特定的定义来完成。例如,int fib(int n) 可以以 n
等于 1 被调用,但是实际上 fib(n) 没有被调用,因为 fib(1) 更加特定。

模式匹配通常要比我这个例子复杂,比如,高级模式匹配系统可以让我们这样做:

int f(int n < 10) { … }
int f(int n) { … }

模式匹配什么时候适用?情况太多了!每当你有一个嵌套着 if
的复杂的数据结构,这时就可以用模式匹配以更少的代码完成得更好。一个很好的例子闪现在我脑海,这就是所有 Win32 平台都提供了的标准的
WinProc 函数(即使它通常被抽象了)。通常模式匹配系统能检测集合也可以应付简单的值。例如,当传给函数一个数组后,就可以找出所有首元素为
1 第三个元素大于 3 的所有数组。

模式匹配还有一个好处即如果需要增加或修改条件,那么不必对付一个巨大的函数。只需增加或修改适合的定义即可。这消除了“四人帮”(GoF)书中的一大类设计模式。条件越复杂,模式匹配就越有用。一旦习惯了它,你就会担心没有了模式匹配的日子如何打发。

Closures

到此我们已经讨论了纯的函数式语言——实现了lambda演算又不包括与丘奇形式系统矛盾的语言——环境里的特性,可是还有很多在lambda演算
框架之外的函数语言的有用特征。虽然一个公理系统的实现可以让我们象数学表达式那样思考程序但它未必是实际可行的。许多语言选择去合并一些函数式的元素而
没有严格的坚持函数式的教条。很多象这样的语言(如Common Lisp)不要求变量是 final
的——可以即处对其修改。他们还不要求函数只依赖于其参数——允许函数访问外部状态。但这些语言也的确包含着函数式的特征——如高阶函数,在非纯粹的函数
式语言里传递函数作为参数和限制在 lambda 演算系统中的作法有些不同,它需要一种常被称为词法(lexical)closure
的有趣特性。下面我给出几个例子。记住,这里变量不再是final的,函数可以引用其作用域外的变量:

Function makePowerFn(int power) {
int powerFn(int base) {
return pow(base, power);
}
return powerFn;
}

Function square = makePowerFn(2);
square(3); // returns 9

函数 make-power-fn 返回了一个函数,它有一个参数,并对这个参数进行一定阶的幂运算。如果对 square(3)
求值会有什么结果?变量 power 不在 powerFn 的作用域中,因为 makePowerFn
已经返回它的栈桢而不复存在。那么square如何工作?一定是这个语言以某种方式将power的值保存了起来以便 square
使用。如果我们再新建一个函数cube,用来计算参数的立方又会怎样?运行环境必须存储两个power的拷贝,每个我们用 make-power-fn
生成的函数都用一个拷贝。保存这些值的现象就被称为 closure。 closure 不只保存宿主函数的参数。例如,closure可能会是这样:

Function makeIncrementer() {
int n = 0;

int increment() {
return ++n;
}
}

Function inc1 = makeIncrementer();
Function inc2 = makeIncrementer();

inc1(); // returns 1;
inc1(); // returns 2;
inc1(); // returns 3;
inc2(); // returns 1;
inc2(); // returns 2;
inc2(); // returns 3;

运行时已保存了n,所以递增器可以访问它,而且运行时为每个递增器都保存了一个 n 的拷贝,即使这些拷贝本应在 makeIncrementer
返回时消失。这些代码被如何编译?closure 在底层是如何工作的?很幸运,我们可以去幕后看看。

一点常识会很有帮助,首先会注意到的是局部变量的生命期不再由简单的作用域限定而是不确定的。那么显然可以由此得出结论它们不再被保存在栈上——反之必须被保存在堆上[8]。这样一来,closure 的实现就象我们前面讨论的函数一样了,只是它还有一个指向周围变量的引用。

class some_function_t {
SymbolTable parentScope;

// …

}

当一个 closure 引用了一个不在其作用域的变量时,它会在其祖先作用域中查找这个引用。就是这样!Closure
将函数式和面向对象的世界紧密结合。当你创建了一个包含了一些状态的类并把它传到别处时,考虑一下 closure。Closure
就是这样在取出作用域中的变量的同时创建“成员变量”,所以你不必亲自去做这些!

下一步的计划?

关于函数式编程,本文作了浅显地讨论。有时候一次粗浅的射猎可能会进展为重大的收获与我也受益匪浅。将来我还计划写写 category
理论,monad,函数式数据结构,函数式语言中的类型(type)体系,函数式并发,函数式数据库等等还有很多。如果我得以(在学习的过程中)写出了上
述诸多主题中的一半,我的生命就会完整了。还有,Google 是我们的朋友。

评论

如果你有任何问题,意见或建议,请发到邮箱 coffee…@gmail.com。很高兴收到你的反馈

===========================

[1] 我在2005年找工作时常常提出这个问题,当时我得到的是数量可观的一脸茫然。想像一下,这些人至少每人会得到30万美元,如果他们理解了他们可以得到的大部分工具。

[2] 这像是个悖论。物理学家和数学家被迫确认他们还不完全清楚是否宇宙万物遵循着可以被数学描述的规则。

[3] 我一直厌恶提供了一堆枯燥的日期,人名和地点的纪年式历史课。对我而言,历史是改变了这个世界的人的生活,是他们行为之后的个人动机,是他们得以影响亿万生灵的体制。所以这个关于历史的小节注定无法完整,只讨论了于此关系及其密切的人物与事件。

[4] 我在学习函数式编程的时候,很不喜欢术语 lambda,因为我没有真正理解它的意义。在这个环境里,lambda 是一个函数,那个希腊字母只是方便书写的数学记法。每当你听到 lambda 时,只要在脑中把它翻译成函数即可。

[5] 有趣的是 Java 的字符串是不可变更的,探讨这一离经叛道的设计的原因也非常有趣,不过在这里会分散我们对原目标的注意力

[6] 大多数函数式编程语言的编译器能通过将递归尽可能转为迭代来进行优化,这被称为尾递归。

[7] 相反未必成立,虽然有时可以证明两端代码等价,但这不是所有情况下都成立。

[8] 这实际上不比存储在栈上慢,因为一旦引入了垃圾回收器,内存分配就成为了一个O(1)的操作。

Powered by ScribeFire.

九月 6, 2007

Sun Microsystems亚太区的工程师Lee Chuk Munn写了一篇不错的小文,介绍一个高效程序开发人员的应该有的7个习惯(Seven habits of effective developers):

  1. Understand the problem

    For instance, Lee said, there are different algorithms engineers can use to write a sorting program.

    To select the right one to use, developers need to first understand
    the size of the data–that the sorting program will be administered
    on–in order to decide which is the right algorithm to use. “Choosing
    the wrong one would put your application in jeopardy in future,” he
    said.

  2. Use appropriate tools

    Quoting American psychologist Abraham Maslow, Lee read: “If the only
    tool you have is a hammer, you will see every problem as a nail.”

    This Maslow concept has become so popular among software developers
    that it has been dubbed the “Golden Hammer” rule, which cautions
    engineers with limited knowledge or training of solutions that they run
    the risk of using only tools they are familiar with, but that may not
    be the most appropriate, when they develop a new program.

  3. Strive for simplicity

    Programmers should develop applications that are:
    Easy to understand;  Easy to explain;  Easy to maintain; andEasy to document.

  4. Keep your code clear

    Lee said: “Put yourself as an author, how would you write something
    that others can easily understand and follow your thoughts? That’s how
    a programmer should code as well.”

  5. Learn to debug

    “Sometimes, there are bugs in your codes, or in other [software]
    libraries that you may have bought or downloaded that you cannot
    control,” Lee said, underscoring the need for developers to “test
    early, test often”.

  6. Leverage what is available

    Developers should also look for solutions or algorithms that are
    already publicly available, such as those highlighted in developer
    forums and mailing lists.
  7. Continue to learn

    Can developers then balance the commercial need to push out
    applications as quickly as possible, with the need to write codes that
    are clean?

    Lee believes they can. “A lot of people say if they don’t have
    quantity time, at least they have quality. That’s not true… You can’t
    have quality time if you don’t put in quantity time,” he said. “There’s
    commercialization [in software development] but there’s also ‘art’ and
    ‘good’ codes. And you can achieve both.” 

如何成为一个高效的程序员,我觉得这是每个开发者应该去思考一下的问题。聪明
不等于高效。在我看来,所谓的高效是能够快速适应问题领域,分析面对的问题,最后快速找到解决方案。在这个过程中,很多品质(Lee Chuk
Munn把他叫习惯)决定了是否高效。比如KISS原则,在一个team中,如果大家都一致认同这个原则,并在产品设计,需求分析甚至编码的时候时刻提醒
自己,那么在brainstorm的时候,很容易达到一致或者可以很快明确当前的主要TODO是什么。相反的,如果有人忽略了KISS,那么很容易纠缠于
细节,导致最后的讨论失去主方向。请记住:把一个系统设计的复杂要比简单来的容易得多

我很喜欢Lee关于写代码的比喻 ——  编写应用程序,就像你再写一本书。写书的时候你应该尽量保持结构清晰并且让你的读者不会觉得很晦涩。如果写书人能够尝试用40%的时间站在读者的角度去思考问题,我觉得这种书成为畅销书的可能性很大。

最后是自我学习,我在面试一些技术人员的时候,我很少问到一些很生僻,很刁难的问题。我更愿意了解他/她在项目中的角色和对新技术的态度。如果一个
开发者愿意学习,并且能够学习,我觉得这种品质要比让他记住一些问题的答案重要的多。现在互联网上公开的文献资料和课程都很多,只要愿意学习,视野一定会
慢慢扩大 —— NextLib也是帮助你管理个人知识的一个好工具 ) 。这是很重要的,还记得“黄金锤子”吧 : 如果你手上只有一把锤子,那么你会把所有问题看成是钉子 ;)

Powered by ScribeFire.

九月 4, 2007

Given below is an example of how to convert any given UNIX timestamp to a System.DateTime.

// This is an example of a UNIX timestamp for the date/time 11-04-2005 09:25.double timestamp = 1113211532;

// First make a System.DateTime equivalent to the UNIX Epoch.System.DateTime dateTime = new System.DateTime(1970, 1, 1, 0, 0, 0, 0);

// Add the number of seconds in UNIX timestamp to be converted.dateTime = dateTime.AddSeconds(timestamp);

// The dateTime now contains the right date/time so to format the string,// use the standard formatting methods of the DateTime object.string printDate = dateTime.ToShortDateString() +” “+ dateTime.ToShortTimeString();

// Print the date and timeSystem.Console.WriteLine(printDate);

Powered by ScribeFire.