January 1, 2008
本博客再度关闭
给东大的申请当然是告吹了。想写的程序一直没写,打不起精神;ML 的书也没有接着看下去。下围棋被一同学的父亲(一个高个儿老头)击败。Windows XP 中了个特傻的病毒,但删它时误删了盗版 Win 用的 OEM 信息,一怒之下装回自己的 OEM 的 Home 版。MBR 被重写,重装 FreeBSD 内核,误选了 Toggle NewFs 选项,Slices 被清空,丢失 400M 书和 500M 歌,所幸我的 Greation 已备份,否则就是倾家荡产。
我现在需要安静,虽然还有很多计算机方面的文章想写。于是我轻轻地走了,正如我轻轻地来~`此番闭博,不知何时再见。
留下未完成的个人主页一个:Yue Time
doubanclaimaeeca142b1906978
December 4, 2007
Re(8):无题
| 哦,原来你也是用windows的,那你学的那些语言都用在什么地方?我总觉得学一个东西总要是用出来的吧,如:某个程序、某个网站、某个系统?不可能总停留在某一个函数和算法的研究上啊?
| 而要发布或部署一个系统,又要用到windows平台,那你写代码总要ide帮助才行啊,如果用记事本写程序的话,完成一个系统那得花多长时间啊?所以好的ide帮助你处理了那些和部署相关的啰嗦的东西,然后你才能安心的处理程序逻辑~
| 而且你的动态语言,我真的不理解,现在流行的python,及perl,这些语言自身是不可存在的吧,他们需要借助某种环境,如必须放在网页中,perl 好像还需在*nix下吧。so,我在windows下就很迷惑,我想写段python或perl代码,却找不到该在哪写?然后就算写好了,确不知道该如何运行~
| 而在大学时也曾想转到nix下,装了freebsd,然后却发现用的很不适应,它里面的编写程序的环境也不是很友好,对着黑黑的窗口和界面普通的kde,感觉无所适从,觉得他不像传说中的那样充满吸引力~
| 于是,在周围同学的CS枪声中我又装回了windows。
| 现在,偶然,碰到你,我又对nix找回写兴趣,我觉得我骨子里还是希望能对nix有比较深入的了解的~
我其实不算是用win的,因为很早开始我就在用*nix的方式工作。这个星期装上了freebsd,甚爽,发现以前学的知识都有了充分的发挥,bash/term好用的不行,再也不用忍受连行编辑都不支持的cmd.exe了。
所谓“学东西拿出来用”,我是这样理解的:写程序,做网站,核心的部分、难的部分不超过20%(除非你写的就是算法库);但这部分却要花去你 80%的时间。剩下来那80%的代码呢?普通的逻辑和简单的调用而已。就知识而言,前者是活的,后者是死的。所以我懒得去搞应用,因为我知道,凭我的能力,到时候对照着文档写都来得及。平时重在练“内功”,以期用兵一时啊。
关于ide,这样跟你说吧。我肯定不会去用ide,但也不可能去用notepad。我们prefer高级编辑器。只要你不用java、c+ +、.net这些特别费话的语言,没怎么配置过的vim,emacs这些东西绝对比ide高效(当然,也可以把emacs弄得比eclipse还拉风,但我不用,就没兴趣了)。它们的指导思想是:让你在写程序的时候用100%的精力思考程序逻辑,用0%的时间输入代码。所以,1. 不帮助你思考。需要有程序帮你思考的语言就不是好语言;2. 不阻碍你思考。敢和人类作对的程序应该去死;3. 使你更快地输入代码。这是高级编辑器提供效率的关键,也是不阻碍你思考的一部分。你觉得ide的代码完成高效吗?vim也有,但不会没事干地帮你匹配文件里没有的单词。而其它的地方ide就没什么戏了。如果你要修改前面第4个单词,你刚把手从键盘上撤下来还没放到鼠标上,vim的用户按3个键esc,3, b就定位到了。举个对你应该特别熟悉的例子:你可能经常需要把程序中的两行并成一行,你常常把光标移过去,然后删除所有自动缩进;vim用户用数字建、k 定位到行,按下shift+j就搞定了。速度,永远不是从帮你输入废话上得来的。
编译、发布、部署、打包程序,这种事情你可能经常让ide代劳。不过你也可能从未想过ide怎么会这么清楚的。它们的能力来源很简单,就是跟*nix学的。make程序,提供了组织程序的一条龙服务的终极武器,每个ide的必备抄袭作品。vs再怎么发展,也不会少了它的nmake.exe,否则它就成了史上最大记事本了。但李鬼终究还是敌不过李逵,nmake这样的东西怎么能敌得过新时代的gnu-make?别说是m$麾下的东西了,就是 ant在它面前也得退避三舍啊,复杂的xml怎么能比一个领域特定编程语言强大?连写makefile都嫌烦,没事还有automake、patch,好了只要把目录组织一下就行了,而且连打补丁都不用你操心了。
这些事关ide的存在价值的东西都在*nix小工具面前黯然失色,其它的东西就更没得比了:gnu下有最强大的编译器、汇编器、调试器,各种dsl程序遍地爬,grep不行上awk,再不行上perl,连接它们的管道四通八达,你可以随心所欲地在编辑器里查看从最底层的gcc那儿传上来的经过数层处理后的信息。
不过懒得出奇的*nixr也有嫌这个也烦的时候,如果有什么语言不用编译不要调试不用部署就更好了。动态语言就是这样的东西。在任意一个平台下装上解释器,一条命令就能执行;高阶函数的出错信息可以跟踪出调用轨迹,不需要调试器;文件间互相说声引用谁就行了,不用部署。写好了代码不知道怎么运行?把相应解释器装上运行一句 <语言名> <文件名> 就行啦!
黑黑的窗口,闪动的光标下是行编辑库,让你在输入命令时获得相当于使用emacs的编辑效率;kde当然没什么意思,仿win仿着玩儿的大玩具一个;试试看怎么把wmaker调成Mac第二,把fvwm做成外星OS,我觉得这个比较有意思。我现在的freebsd就是用的wmaker窗口管理器,装上几个顺手的gtk程序,剩下来的让vte终端全包。性能好到不行,启动图形界面4秒钟。Unix的重点不在于刚装好时的配置如何,而在于极限的能力是否能满足你的意图。就拿winer们常指责*nix的驱动程序一项来说,其实os里这些驱动都有了,你所要做的只不过是修改某个配置文件中的一两行。这帮winer自己数典忘祖,忘了win装完后必须下载驱动才有全屏显示和声卡支持,而且如果你没有网卡驱动就只能哭去了!
再如装软件。win装软件要搜索多长时间才能下到,还得提防有没有恶意软件,点多少个next才能装完,装完想删掉还卸不干净,装着装着注册表就破50M了。pkg、yum、apt这些东西多方便,你只要知道软件名,输一行命令就能全搞定。有些不明事理的人还指责*nix软件怎么会对其它软件包有依赖。其实这才是*nix的魅力所在:开发出来的东西大家用,做的好的就是库。事实上,只有本地安装命令才会指出依赖性问题,使用自动安装连这些被依赖的软件也一并装了,反正想删就删,*nix分区只要有5%的空间就能保证没有磁盘碎片,多装没坏处。
最后想说的是玩游戏。我用win的时候打了一年魔兽,现在在班上几乎无敌;但又能怎么样呢?我下围棋估计能全灭学校的人,然而没什么人知道。由此我意识到,游戏终究是一种逃避,想把在其它地方失去的虚荣心找回来。领悟了这一点,我也就不再玩游戏了;有时间就下围棋,没时间下就国际象棋, *nix下的gnugo和gnuchess都是普通棋类程序中的强者(不过围棋程序现在的算法还做不了太强,前者还不是我的对手),杀败了它们还可以上 igs和ics,生活很幸福~`
November 8, 2007
复习 Perl
今天我去报名下一次托福模考,顺便去了书店转转——很长时间没关心计算机方面的书籍了,我唯一想看到只是《精通正则表达式》,也算是 “门当户对”。不过还是留意了一下Perl方面的书籍。《Perl入门》是一本小书,42块钱买来不值,于是我干脆发挥一下我在编程语言方面的功底,当场看完了事。
学习了更多编程语言原理方面的知识之后再来考察Perl语言果然有一些新感受。Perl关键字众多,但其实并不复杂:undef 好记还是 undefined?也没有 NULL 啦 nil 啦什么令人混淆的东西。空变量就是没定义,空表就是空表嘛。其它的关键字设定,如果你能忍受shell的fi什么的恶心东西那有什么理由不能忍受 Perl?
字符串用常量替换和括号代换体现是Perl的首创。学习前者的有PHP,后者有Ruby,不过弄来弄去还是不能取代Perl强大的上下文精神——到目前为止的语言都不能在字符串代换时展开列表甚至是展开列表切片就是一个证明。Ruby照抄了括号体系,但还是不敢抄Perl太多东西。在正则表达式里展开变量,IO描述符呢?它会和别的“纯”的语言一样露出叫你服从安排的嘴脸。
Perl的正则表达式已经红的发紫了——正如《学习Perl》一书骄傲的自夸——“很多语言和库都会以‘支持Perl风格正则表达式’作为卖点”。$` $& $' 这三个变量的设计更是叫我拍案叫绝!一方面,这三个变量对于实现不修改原数据的函数式编程来说是至关重要的,它可以把匹配过程表述为“过去,现在,将来”;另一方面,英语中常常使用`号作为应用开头以区别'x缩写形式,所以这种命名十分清晰,意思就是“开头,和,结尾”!(Perl使用的这种“命名推导”的体系和我早期设计的Mazy(t)是类似的:))最后谈一点语义方面的东西。Perl的函数参数传递,本质上是只传递了一个变量(这与Ruby、Groovy之流的简单模仿具有核心上的不同),然后靠类似模式匹配的语法对它们进行命名。这个机制和我的Mazy (i)语言的做法是相同的;只不过我扩展了模式匹配的能力并把它提高到Lambda演算兼容的层次罢了。至于这种手法的优势——你不需要再去思考是用 max(a,b,c)还是max([a,b,c])甚至是apply(max,[a,b,c])了!Perl懒人们笑哈哈...由此可见,Python3 新加入的取代apply的语法,只能说明Guido大学课程没学好~~`
付:由Perl想到的Unix法则
不知道应该称为“法则”还是“通病”——概念充分实例化,尽可能地自圆其说。以Vim为例,普通模式,各种命令一套意思;换个环境或者开头控制符(这是Emacs吧?),命令不变,变成表达相反含义或对不同视角的对象应用合理解释后的命令;实在不能合理解释,怎么着也要凑一个上去。Perl也是这样。三种上下文标记,换成默认变量没法解释了,还要凑个类似的;散列怎么会有切片?《Perl入门》还强词夺理;while(<STDIN>) 这样的用法没意义,就凑一个方便的意义上去。不过还是那句话,“倒也门当户对”就是了。为了让大家能在看人打字手抽筋的时候安心喝茶,Larry Wall 用心良苦啊~`
September 22, 2007
基于消息传递的 Scheme OOP
;; 这里只有部分定义,快速排序和二分法查找的代码就不帖在这儿了
;; 这里是 slot 类型的定义,Scheme 中不能动态 eval,只能用点别的招数
(define (make-slot name act) (cons name act))
(define (slot-name s) (car s))
(define (slot-act s) (cdr s))
(define (slotstring a) (symbol->string b)))
;; 主要的转换宏,语法比较丰富,但至少要带有一个 that 块
;; 语法可以只给出 that 块:(class (that (slot-name1 slot-act1) ...))
;; 也可以指定继承,要求在 class 关键字后出现原型对象:
;;(class extended-obj (that (slot-name1 slot-act1) ...))
;; 在 that 块之后还可以加入 where 块,这样可以指定不对外可见的类属性
;; 需要注意的是,where 块中的定义不能引用 self 等特殊命名,that 才行
;; 另外外部只能引用 origin, has-slot?, slot-names 3个特殊命名
(define-syntax class
(syntax-rules (that where)
((_ that-block)
(class (absobj) that-block))
((_ org that-block (where def ...))
(letrec (def ...) (class (absobj) that-block)))
((_ org (that (slot val) ...))
(lambda ()
(letrec ((slot val) ...)
(let* ((origin org)
(slots (list-qsort slotvector (map slot-name slots)))
(slot-acts (list->vector (map slot-act slots)))
(has-slot? (lambda (v)
(vector-bsearch symbol index -1)
(vector-ref slot-acts index)
(case verb
('origin origin)
('slot-names slot-names)
('has-slot? has-slot?)
(else (origin verb))))))))
self))))
((_ that-block where-block)
(class (absobj) that-block where-block))))
;; 这里需要注意的是,因为嫌烦,absobj 被实现为了一个空壳子
(define (absobj)
(lambda (verb)
(display "This object can't handle ")
(display verb)
(newline)))
测试一下:
> (define c1 (class (that (x 10))))
; no values returned
> (define c2 (class (c1) (that (z 16) (y 12))))
; no values returned
> (define o2 (c2))
; no values returned
> (o2 'x)
10
> (o2 'non-slot)
This object can't handle non-slot
#{Unspecific}
> (o2 'slot-names)
'#(y z)
> (o2 'origin)
#{Procedure 8647 (self##497 in c1)}
输出好像很古怪的样子~`我用的环境是 Scheme 48 虚拟机。
这儿的例子只覆盖了一小部分,剩下的比如 where 就自己慢慢玩吧。
Scheme 的“卫生宏”不但强大,还能制止你设计不好的语法,有意思。
消息传递真的很有意思。留个小题目,增加一个特殊方法 clone,把继承机制调整为差异继承+纯基于对象,类似 IO 语言 的面向对象机制。
这个“系统”的完整代码在此下载,升级版本就算了吧 :)
September 16, 2007
Mazy i 编程语言定义 (1,2)
;; 纯属写着玩儿,尚未最终决定的版本已经与此差别很大,比方说 第二章。
;; 因为修改麻烦,本博客有关 Mazy(i) 设计方面的文章从此只发表第一稿。
1. 前言
Mazy i 编程语言,是一种用一阶λ演算的观点解决普通数学问题的纯函数式编程语言,具有极其简洁的语法和一定的通过数学表示推导解决数学问题的能力。它被设计为一种附合熟悉普通数学记法的用户在思考函数式编程时使用的简记记法的语言。它在语法、语义、实现方面的设计必须附合这一要求。第2章对此给出了概述性的解释。
第3章具体描述 Mazy i 的所有语法,每个语法后有用 Mazy i 扩展记法给出的语义描述,某些语法可能附上实现指导。
第4章描述了 Mazy i 程序文件中位于代码之前的文件说明区块,以及程序文件的加载、执行方式。
第5章描述了 Mazy i 内置库中的所有函数和宏,重点概念会给出 Mazy i 扩展记法的描述。
附录A总结第3章给出的用简化的 BNF 表示的 Mazy i 形式语法。
附录B列出了部分参考文献。
Mazy i 指的是 Mazy 语言第三版。Mazy 代表数学和懒惰,不是混乱和忙碌。字母 i 源自 Mazy 语言的版本命名传统,从第一到第七版分别为 s-t-i-n-g-e-r,排版字体为斜体,下标。Stinger 这个单词是作者姓氏,此外没有其它含义。
以下用 Mazy 代指 Mazy i。
2. 概述
编程语言必须对其设计意图有强大的表现力。强大基于完备,完备基于简单。
Mazy 的设计意图是作为一种可执行的简记记法帮助熟悉简单数学表示法的用户思考、编程或教学。也就是说,Mazy 试图用程序的观点去解释范围不超过基本函数、数列的数学记法。当然,作为基于λ演算的编程语言,Mazy 还有足够的表现力去解决更复杂的问题。
例如,Mazy 中的列表可以作数列解释。对于给定的列表 l,其第 n 项在 Mazy 中可直接表示为 l n。同时,使用递推的观点可以写出求列表长度的函数。下面的函数就是一个合法的 Mazy 程序:
len ls =
0, ls = ()
len(tail ls) + 1.
Mazy 可以把列表看作是第一项 x 与剩余项 xs 的组合 x:xs,所谓 tail 函数不过是:
tail (x:xs) = xs
那么 len 函数就可以直接写成:
len () = 0
len (x:xs) = len xs + 1
以上是 Mazy 在语法方面试图实现其设计意图的一个例子,用到了单一控制结构和函数单参数的语法特性。其它语法,包括块结构、列表领悟、λ形式等,以及为保证这些语法的等效性而设计的 Mazy 编程语言语义,例如完全无副作用,单一数据类型,单一数据结构,完全基于模式匹配等,都会在本文档中给出详细描述;附加的实现指导,例如尾递归优化,列表、元组共存模型,缓存机制等,则用于指导 Mazy 的实现人员更好地保证基于这些语义的程序高效地执行,以防止用户被迫转用其它语义思考和编程。
September 12, 2007
Mazy(i) plain-BNF 修订稿
comment :: ;;.*(?=\n)
Number :: \d+(\.\d+)?(_[+-]?\d+)?
Identifier :: [\$]\w*[\?']?
program ::
sentence
sentence \n program
sentence ::
functionDefinition
patternMatching
functionDefinition ::
Identifier pattern = Experssion
Identifier pattern = \n block .
block ::
Experssion
program \n Experssion
Experssion ; whereBlock
program \n Experssion ; whereBlock
clause
program \n clause
clause ; whereBlock
program \n clause ; whereBlock
clause ::
subClause
subClause \n Expression
subClause ::
Expression , Expression
Expression , Expression \n subClause
whereBlock ::
patternMatching
patternMatching \n whereBlock
patternMatching :: pattern = Experssion
Expression ::
Number
Identifier
Lambda
Compound
Operation
Comprehension
List
( Expression )
Lambda :: [ pattern -> block ]
Compound :: [ block ]
Operation ::
Expression Expression
prefix Expression
Expression midfix Expression
prefix :: oneof
+ - ~ #
midfix :: oneof
midSymbol
% = ~= < > >= <= & | . .. : ++ <-
midSymbol :: oneof
+ - * / ^ ** //
Comprehension :: ( Expression => sequence )
List ::
( )
( Expression , )
( sequence )
sequence ::
Expression , Expression
Expression , sequence
pattern ::
_
Number
Identifier
patternOperation
patternList
( pattern )
patternOperation ::
pattern : patternList
pattern midSymbol Number
Number midSymbol pattern
patternList ::
( )
( pattern , )
( patternSequence )
patternSequence ::
pattern , pattern
pattern , patternSequence
最重要的变更还是新增了 block 抽象语法,没想到的时候是很痛苦的。
为语法分析增加的额外产生式也有了,这样语法和语义基本上就定下来了,下面开始写词法分析器——相当好写,输出的数据结构上用点技巧(平衡符号的开始符号指出平衡点),连带 AST 生成器一并好写。
Ray 语录:做语言要做到什么地步才叫绝?想再加1条产生式时,烦于必须再加一系列产生式;想再减1条产生式时,苦于必须再减一系列产生式——这就是所谓的“精益求精”。
September 8, 2007
Mazy(i) BNF 第一稿
Digits :: [0-9]+
Identifier :: [a-zA-Z]+_*'*
Comment :: ".*?"
Program ::
Sentence
Sentence \n Program
Sentence ::
FunctionDefinition
PatternMatching
FunctionDefinition ::
Identifier Pattern = Experssion
Identifier Pattern = \n Clause .
Identifier Pattern = \n Clause ; WhereBlock .
Identifier Pattern = \n Program Clause ; WhereBlock .
WhereBlock ::
PatternMatching
PatternMatching \n WhereBlock
PatternMatching ::
Pattern = Experssion
Expression ::
Number
List
Identifier
Operation
Condition
Lambda
( Expression )
Number ::
Digits
Digits.Digits
Digits_Digits
Digits.Digits_Digits
List ::
( )
( Expression , )
( Sequence )
Comprehension
Comprehension :: ( Expression => Sequence )
Sequence ::
Expression
Expression , Sequence
Operation ::
Expression Expression
Prefix Expression
Expression Midfix Expression
Expression Postfix
Condition ::
[Clause]
[Expression ; WhereBlock]
[Clause ; WhereBlock]
Clause ::
SubClause
SubClause \n Expression
SubClause ::
Expression , Expression
Expression , Expression \n SubClause
Lambda ::
\ Pattern -> Expression .
\ Pattern -> Expression ; WhereBlock .
Prefix :: oneof
+ - ~ #
Midfix :: oneof
+ - * / % ^ ** // : ++ -- = ~= < > >= <= & | ! !! $ .. <-
Postfix :: oneof
++ -- !
Pattern ::
PatternAtom
PatternList
Pattern : PatternList
PatternPreOpt Pattern
Pattern PatternMidOpt PatternAtom
PatternAtom PatternMidOpt Pattern
Pattern PatternPostOpt
PatternList ::
( )
( PatternAtom , )
( PatternSequence )
PatternSequence ::
PatternAtom
PatternAtom \n PatternSequence
PatternAtom :: oneof
Identifier Number _
PatternPreOpt :: oneof
- ~
PatternMidOpt :: oneof
+ - * / ^ ** // :
PatternPostOpt :: oneof
++ --
新语言想设计成为基于模式匹配的语言,于是进行了模式匹配完备化的努力,最后搞出了一个带有推导功能的东西,这样函数就可以只接受一个参数,同时满足了参数之间“关系”表达的要求——终于找到可以超过 Haskell 的地方了 :)
加入了 Lambda 表达式,且带有作用域的语法都有了一个 WhereBlock,但功能其实相当于 Scheme 中的 let* 而不是 Haskell 中的 where;
列表相当于 tuple 和 list 二者的结合,列表领悟也有了;
其它一些纯属使语言更“漂亮”的小改动不值一提,感兴趣的自己看吧。
September 6, 2007
我看 D 语言
August 25, 2007
用 Python 写了个背单词小工具
注:此“软件”已被作者开为 Google Code 项目 Rewords 。背单词关键在于重复,单词能不能背得取决于你看了它多少遍。把一个单词表(wordlist)中的每个 list 分成许多个 section,反复地浏览记忆就能记住。基于此,昨晚我用 Python 写了一个背单词的小工具,暂定名 Rewords 。
#!/usr/bin/python
# Filename: rw.py
import sys
def locate (f, i, n = 1):
if n == i:
print f.readline()
return f
if f.readline() == "\n":
return locate(f, i, n+1)
return locate(f, i, n)
def struct (f, st = []):
ln = f.readline()[:-1]
if ln == "":
return st
return struct(f, st+[ln])
def conmat (ls, l, tmp = []):
if len(tmp) == l:
return [tmp]+conmat(ls, l)
if len(ls) <= l and tmp == []: return [ls] return conmat(ls[1:], l, tmp+[ls[0]]) def inter (st): '''usage:
n : next word
p : prev word
d : down to next section
u : up to prev section
l
def term (rt, lt = [], n = 0, cur = 'n'):
def help ():
print inter.func_doc
term(rt, lt, n, cur)
def next ():
if rt == []:
term(lt , rt, n, 'n')
print rt[0]
term(rt[1:] , lt+[rt[0]], n, 'n')
def prev ():
if lt == []:
term(lt , rt, n, 'p')
print lt[-1]
term([lt[-1]]+rt, lt[:-1], n, 'p')
def down ():
if n == len(st)-1:
term(st[0], n=0)
term(st[n+1], n = n+1)
def up ():
if n == 0:
term(st[len(st)-1], n=len(st)-1)
term(st[n-1], n = n-1)
def to (p = 1):
num = (p-1)%len(st)
term(st[num], n = num)
def err ():
print " # unsupported command"
term(rt, lt, n, cur)
cmd = sys.stdin.readline()[:-1]
if cmd == "q": quit()
if cmd == "h": help()
elif cmd == "":
if cur == 'n': next()
elif cur == 'p': prev()
elif cmd == "n": next()
elif cmd == "p": prev()
elif cmd == "d": down()
elif cmd == "u": up()
elif cmd.isdigit(): to(int(cmd))
elif cmd[0] == 'l' and cmd[1:].isdigit():
start(int(cmd[1:]))
else: err()
term(st[0])
def quit (m = ""):
if m:
print m
sys.exit(1)
sys.exit(0)
def start (wl = 1):
'''usage: python rw.py
The wordlist named
program will start with the [list] or first part of the wordlist;
the each section will contain [section] or 8 words. '''
inter(conmat(struct(locate( \
open(sys.argv[1] if len(sys.argv) > 1 \
else quit(start.func_doc)), wl)), \
int(sys.argv[3] if len(sys.argv) > 3 else 8)))
print "rw.py - Rewords 0.1a by lichray\n"
start(int(sys.argv[2] if len(sys.argv) > 2 else 1))
程序是自省的,用起来应该没问题。无参数运行 python rw.py 显示帮助。
参数1 是单词表文件名,这个文件需要被空行分解为多个 list,每个 list 的第一行是标题;
参数2 是启动时加载的 list 编号,默认为 1;
参数3 是每个 section 包含的单词数,默认为 8;
进入互交描述后参考帮助,输入 h 命令,回车。
我这儿有一个可用的单词表——Barron SAT 词表,但存在一个小 Bug,不知为什么这个词表只能加载前13个 list,想帮忙的人留个邮箱地址,我把东西都发过去。
另外 locate() 函数还不能处理 readline() 越界的情况,这部分没完成。
最后根据我的代码风格,猜猜看我最近什么语言用的比较多?
August 18, 2007
OOP 诡异教程(下)
这是最终确定的 JavaScript 基于消息传递编程风格的文章“OOP 诡异教程(上)”的下篇。原文地址:http://let-in.blogspot.com/2007/06/oop.html。原来的想法是以风格开头,谈到 JavaScript 的内部机制,但作者 lichray 迟迟没有动键盘,认为不如利用已有的风格做一套机制出来,这样可能更有意义。于是,就有了这个更加“诡异”的下篇。
四. 扩展的实现
上文最后给出了一个“看上去很美”的基于消息传递的编程风格,比如构造一个 People 类的代码类似:
function People () {
var money = 0
function setMoney (dollars) {
money = dollars
}
function pay (dollars) {
money -= dollars
}
return (function (verb) {
return eval(verb)
})
}
有了这样的语法我们就可以描述不少句子了。但是存在一个问题:现实中的 Objects 之间是存在关系的——比如,forrest 是个 IQ 为 75 的傻子,傻子是 People 的一种。而我们仅仅是生搬硬套了一种语法而割裂了这种 "is-a" 关系。现在我们的工作,目的之一就是让这样一个“真切”的世界从我们已有的编程风格的地基上拔地而起。
到底应该怎样做才能使 Fool 产生的对象都能响应 People 的消息呢?我们要给 Fool 产生的对象(也就是返回的那个匿名函数啦)都添加这样一种能力:如果在 Fool 中响应不了消息,那就反馈给 People 响应。
function Fool (iq) {
var IQ = iq || 0
function init (iq) {
IQ = iq
}
return (function (verb) {
try {
return eval(verb)
} catch (e) {
return People()(verb)
}
})
}
js> forrest = Fool()
js> forrest('init')(75)
js> forrest('IQ')
75
js> forrest('money')
0
五. 语法扩展和代码生成
这下代码量增加了很多,强迫潜在的使用者们在创建每个类时都这样写那实在是令人抓狂。本来这篇文章应该不提此类问题的解决,但考虑到有益于读者理解“机制”这个抽象概念,这里给出一个可行的方案——把普通的类代码用 Function() 函数重编译为可用的 JavaScript 函数。也就是说,我们能给出类扩展的代码并指定被扩展的类来获取类似上文的代码:
Fool = extend('People()', function (iq){
var IQ = iq || 0
function init (iq) {
IQ = iq
}
})
为了方便字符串操作,我们希望编译后的代码的参数部分(如 People())都集中出现在一个位置且尽可能便于定位。在函数头添加一句
var origin = People()
当然是可行的,这样还能使 Fool 内部显式引用到其超类。但这样还不够漂亮。我们修改编译后的样例代码为:
function () {
return (function (origin) {
var IQ = 0
function init (iq) {
IQ = iq
}
return (function (verb) {
try {
return eval(verb)
} catch (e) {
return origin(verb)
}
})
})(People())
}
这个利用参数传递变量的小技巧不值得学习,实际效率不高。但在这篇文章中,这样绑定特殊变量的技术是标准方案。
那么,extend() 函数的实现为:
function extend (originc, code) {
function argsArea (code) {
// 题外话,正则表达式也有不值得使用的时候
return code.slice(code.indexOf('(')+1, code.indexOf(')'))
}
function bodyCode (code) {
// 不用 trim() 了,没事儿找事儿
return code.slice(code.indexOf('{')+1, code.lastIndexOf('}'))
}
function format (body) {
var objc = bodyCode(function () {
return (function (verb) {
try {
return eval(verb)
} catch (e) {
return origin(verb)
}
})
}.toString())
return 'return (function (origin) {'+body+objc+'})('+originc+')'
}
var $ = code.toString()
return Function(argsArea($), format(bodyCode($)))
}
这样前文提到过的 extend 的实例代码就可以正常运行了,测试代码不再重复。
六. 机制完备化
这样,我们的基于消息传递编程风格的一套面向对象机制就确定下来了。机制是宪法,是语言的根本大法,有了它,我们就可以通过修改代码生成器,很快地给这套机制进行完备化。
想法有很多,例子只举两个。
第一个例子:类的定义中应该能直接引用到将产生的对象 self。答案只有一句话:把返回的那个作为对象的匿名函数命名为 self。
第二个例子:既然是单继承模式,应当存在一个顶层类 AbsObj,使没有指定继承的类自动继承它。答案也只有一句话:在 extend 函数体第一行添加代码:
if (arguments.length == 1) {
code = originc
originc = 'AbsObj()'
}
然后手工构造设计 AbsObj 类,为空也无所谓。不过当然了,一般都会给顶层类添加一些全局性质的消息绑定。由于是“底层操作”,基本上都需要修改 extend 函数。做了一个简单的:
function AbsObj () {
//检测是否能响应此 verb,要再用一次异常处理
function canHandle(verb){
try {
// 别担心这里的 self 会传递不过去
self(verb)
} catch (e) {
return false
}
return true
}
function toString() {} // 这个搞起来其实很麻烦~`
var self = function (verb) {
return eval(verb)
}
return self
}
js> Obj=extend(function(){x=5})
js> o=Obj()
js> o('canHandle')('x')
true
js> o('canHandle')('y')
false
文章写完了,小结一下。消息传递的编程不仅仅是一种代码风格,还可以成长为一种完备的机制。这种完备性远不只是这两篇加起来不到300行的文章所能覆盖的(例如非常彻底的“万物皆对象”,因为只要是能响应消息的函数,连接一下 AbsObj 就是合法对象了;类,函数都可以),大家可以试着玩一玩,顺便体会一下这个计算模型的透明和强大。
另外,熟悉函数式编程的朋友可以帮忙思考一下:这样一个基于闭包变换的计算模型实质上是函数式的,再配合动态的函数式的对象级继承(用一个匿名类代换一下)就能在纯 FP 真正下实现 OOP 了。可惜的是每一次更新操作都要重新生成对象,性能代价大了点,不知道大家有什么好想法。
July 27, 2007
functional.js 介绍及源码分析
作者 lichray 对刚刚在网络上现身的 JavaScript 函数式编程库 functional.js 进行了详尽的解读。
functional.js 是模仿 Haskell 语言标准库 Prelude 制作的函数式编程库,主要实现了:
- 扩展的克里化函数
- 运算符函数化
- 紧缩的匿名函数语法
- 无须指定参数的匿名函数语法
- 函数向导语法
- 基本的通用列表操作
- 部分扩展基于对象化
其中,扩展语法由字符串表示。未能实现的特性有:
- 尾递归优化
- 模式匹配(包括参数匹配、列表匹配、情况分析)
- 惰性运算(包括无穷列表)
- 列表领悟
- 扩展绑定、同时绑定
- 其它列表操作(以及对于列表操作的基于对象化)
下面我们一边分析源代码,一边讲解库的用法。
一、库安装和概览
functional.js 库的所有用户级操作分为3个部分:
- 全局操作,绑定在全局对象 Functional 上,主要是高阶函数操作和列表操作,所谓库安装即把这些内容可选的、安全地复制到全局环境
- 函数扩展,实现特殊高阶函数特性的工具(特供内部)
- 语法扩展,绑定在 String.prototype 上,负责将字符串表示的 lambda 语法翻译为相应的高阶函数(特供内部)
下面是安装函数 Functional.install 的源代码(中文注释为笔者所加,下同):
Functional.install = function(except) { // except 参数是一个对象,不加载这些操作
var source = Functional,
target = window; // 复制操作到全局环境 window,仅限于浏览器环境
for (var name in source)
name == 'install' // 当然,不能把 install 复制到 source
|| name.charAt(0) == '_' // 命名开头为 _,私有属性
|| except && name in except
|| {}[name] // work around Prototype
|| (target[name] = source[name]);
}
一般只要执行 Functional.install() 一句即可。
二、高阶函数操作
1. Functional.compose ([Function]) // 匿名的参数类型指的是 arguments 的类型,下同
接受一列参数个数被认为相等的(允许 Currying 算子)函数为参数,返回一个函数,它接受一定的参数,能够对它们累积倒序 apply 那列函数。
示例:compose('1+', '2*')(2) => 5
2. Functional.sequence ([Function])
累积 apply 的顺序为参数顺序,为 compose 的反序。
示例:sequence('1+', '2*')(2) => 6
以上两个操作亦可见于 Function.prototype,用法:'1+'.lambda().sequence('2*')(2) ==> 6
3. Function.prototype.flip ()
返回一个函数,是原函数对象 this 参数接受顺序颠倒后的版本,不应属于函数扩展类。
示例:flip('a/b')(1, 2) => 2
4. Function.prototype.saturate ([]) {
返回一个函数,是原函数对象 this 忽略自己接受的参数,仅接受指定参数的版本。
形式:f.saturate(args...)(args2...) == f(args...)
5. Function.prototype.aritize (n::Number) // 有名的参数类型由 :: 指定,下同
返回一个函数,是原函数对象 this 忽略自己接受的参数列表中下标为 n 的参数的版本。
6. Function.S (f, g::Function)
以单个大写字母命名的是函数的原子操作,应被收入 Functional 对象,这个很奇怪。
形式:S(f, g)(args...) == f(g(args...), args...)
三、通用列表操作
1. 绑定在 Functional 对象上的部分完全照抄 Haskell Prelude 以及 Clean 的命名,它们是:
- map(f, [x1, x2...]) = [f(x, 0), f(x2, 1), ...]
- foldl, reduce(f, init, [x0, x1, x2]) == f(f(f(init, x0), x1), x2)
- filer, select('%2', [1,2,3,4]) -> [1, 3]
- foldr(f, init, [x0, x1, x2]) == fn(x0, f(x1, f(x2, init)))
- some(f, [x1, x2, x3, ...]) == f(x1) || f(x2) || f(x3)...
- every(f, [x1, x2, x3, ...]) == f(x1) &&amp;amp;amp;amp; f(x2) && f(x3)...
以上操作的介绍网上到处都是,不再赘述;但有一点不同,即它们除了接受正常参数之外,还在最后接受一个可选参数 object::Object,它被用于指定操作执行的对象/环境。
另外,这些操作全部基于命令式风格实现,对于没有尾递归优化的 JavaScript 来说,效率有保障。
四、群体谓词操作
1. Functional.and ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 true,返回 true;否则返回 false。
形式:and(f1, f2...)(args...) == f1(args...) && f2(args...)...
示例:and('>1', '>2')(2) => false
2. Functional.or ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全为 false,返回 false;否则返回 true。
形式:or(f1, f2...)(args...) == f1(args...) || f2(args...)...
示例:or('>1', '>2')(2) => true
3. Functional.not = function(fn::Function)
返回一个函数,是参数返回的布尔值的函数(谓词,下同) fn 取否的版本。
形式:f.not()(args...) == !f(args...)
4. Functional.equal ([Function])
接受一列函数为参数,返回一个函数,它接受一个参数,对该参数 apply 那列函数,如结果全部 == ,返回 true;否则返回 false。
形式:equal(f1, f2...)(args...) == f1(args...) == f2(args...)...
示例:equal()() => true // 特殊情况
五、函数扩展
这一章仅仅是介绍内部实现。
1. Function.prototype.bind (object::Object,[])
返回一个函数,作为 this 函数对象的副本,使其将在 object 环境下执行,并额外携带参数。
形式:f.bind(obj, args...)(args2...) == f.apply(obj, [args..., args2...])
2. Function.prototype.curry ([])
这是实现克里化特性的关键函数,思想来自网络。
Function.prototype.curry = function(/*args...*/) {
var fn = this;
var args = [].slice.call(arguments, 0);
return function() {
return fn.apply(this, args.concat([].slice.call(arguments, 0)));
};
}
返回那个传说中的可在参数不足时分步调用的函数——Currying 算子。
形式:f.curry(args1...)(args2...) == f(args1..., args2...)
其它的 curry 类函数有:
- rcurry,对从右边开始缺少参数的函数作克里化
- ncurry,不接受全部参数就不 apply 参数的版本
- rncurry,前者的反序版本
- uncurry,作者一再强调,这不是 curry 的反转版本。它会拆分出第一个已得参数,形式为:f.uncurry(a, b...) == f(a)(b...)
3. Function.prototype.partial ([])
在此函数定义之前,有定义 _ = Function._ = {} 。结合它们可以允许你像在 Haskell 中那样在参数列表中用 _ 忽略参数。但现在空谈是没用的,要结合第七章的语法扩展。
4. Function.prototype.guard (guard:>Function, otherwise)
类似的,是一个允许在函数定义中使用向导功能的工具,尚缺少语法扩展支持。
形式:f.guard(g, h)(args...) == f(args...), when g(args...) is true
f.guard(g ,h)(args...) == h(args...), when g(args...) is false
六、工具函数
1. Functional.invoke (methodName::String, [])
示例:invoke('toString')(123) => "123"
2. Functional.pluck (name::String)
示例:pluck('length')("abc") => 3
3. Functional.until (pred:>Function, fn:>Function) // 用 :> 表示将参数强制转换类型
Functional.until = function(pred, fn) {
// 使用时参数会被强制转为 Functional 的函数,参数可为字符串
fn = Function.toFunction(fn);
pred = Function.toFunction(pred);
// 返回一个接受一个参数的函数,
return function(value) {
// 它不断对此参数 apply 函数 pred,
while (!pred.call(null, value))
// 并用 fn(value) 的值更新 value,
value = fn.call(null, value);
return value; // 直到测试结果为 true。
}
}
类似 Haskell 的 until,是一种函数式的循环,用命令式风格实现。
4. Functional.zip ([])
特别注意,此 zip 并非 Haskell 中的 zip,它接受可变参数列表而不是列表的列表。
形式:zip(a, b...) == [[a0, b0], [a1, b1], ...]
以上的章节中绑定在 Functional 上函数都可作为 Function 的对象方法直接使用,我们看这一行:
Functional.__initalFunctionState =
Functional._startRecordingMethodChanges(Function.prototype);
前文对它们作出了定义,这里忽略。用法:name(arg, args...) == arg.name(args...)。
七、语法扩展
1. String.prototype.lambda ()
把字符串表示的字符串翻译为函数扩展可接受的函数,进一步转为 JavaScript 函数。
String.prototype.lambda = function() {
var params = []; // 存储字符串形式的参数的列表
var expr = this;
// ECMAsplit 是作者为兼容 IE6.0 所写的 split 版本
var sections = expr.ECMAsplit(/\s*->\s*/m); // 使字符串被 '->' 分割
/* 注意,分割的结果支持超过任意个 '->',下面会发现,
-> 9 或者
x -> y -> x+y 这样的代码也会被正确理解。
*/
if (sections.length > 1) {
// 这就是所谓的“正确理解”了
while (sections.length) {
expr = sections.pop();
// 然后把参数打碎,再重组为 JS 可识别的参数语法
/* 也就是说,x y -> x*y+2 和
x,y -> x*y+2 都可被接受。
*/
params = sections.pop().split(/\s*,\s*|\s+/m);
// 装配成代码,顺便支持尾递归语法
sections.length && sections.push('(function('+params+'){return ('+expr+')})');
}
} else if (expr.match(/\b_\b/)) {
params = '_'; // 忽略参数的前奏,下文判断
} else {
// 这里处理运算符表达式参数缺失的情况,相当于运算符函数化
// 分为前缺失和后缺失两种情况,
var leftSection = expr.match(/^\s*(?:[+*\/%&|\^\.=<>]|!=)/m);
var rightSection = expr.match(/[+\-*\/%&|\^\.=<>!]\s*$/m);
/* 注意,前缺失类似 *2,后缺失类似 2*,复杂表达式同样支持
此外,前后都缺失也可以,比如 * 甚至是 *3*
*/
if (leftSection || rightSection) {
// 翻译缺失代码的技术:用 $1、$2 代换参数
if (leftSection) {
params.push('$1');
expr = '$1' + expr;
}
if (rightSection) {
params.push('$2');
expr = expr + '$2';
}
} else {
// 这个地方就有点意思了;它使得函数支持参数指定缺失
/* 比如 x*y 就已经是一个函数了,相当于 x y->x*y
作者还特别防止了一个 bug,即对象属性访问语法中,
属性部分不被认为是未指定的参数。例如
obj.pro + 4 这个函数,只有 obj 一个参数
而且,this 和 arguments 不会被认为是未知数。
*/
var vars = this.replace(/(?:\b[A-Z]|\.[a-zA-Z_$])[a-zA-Z_$\d]*|[a-zA-Z_$][a-zA-Z_$\d]*:|this|arguments|'(?:[^'\\]|\\.)*'|"(?:[^"\\]|\\.)*"/g, '')
.match(/([a-z_$][a-z_$\d]*)/gi) || [];
for (var i = 0, v; v = vars[i++]; )
params.indexOf(v) >= 0 || params.push(v);
}
}
return new Function(params, 'return (' + expr + ')'); // 把代码装配成函数对象
}
八、过滤器生成器
仅供特别好学的同志们参考。
1. Function.prototype.prefilterObject (filter::Function)
形式:fn.prefilterObject(filter).apply(object, args...) == fn.apply(filter(object), args...)
2. Function.prototype.prefilterAt (index::Number, filter::Function)
形式:fn.prefilterAt(i, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(a_{i}), ..., a_{n})
3. Function.prototype.prefilterSlice (filter::Function, start, end::Number)
形式:fn.prefilterSlice(i0, i1, filter)(a1, a2, ..., a_{n}) == fn(a1, a2, ..., filter(args_{i0}, ..., args_{i1}), ..., a_{n})
九、其它用户级函数
1. Functional.id = Functional.I = function(x) {return x};
2. Functional.constfn = Functional.K = function(x) {return function() {return x}};
3. .toFunction ()
在 String.prototype,Function.prototype,Function(需要参数 fn::Function) 上都有绑定,把对象转换为一个合适的 Functional 函数。但你不需要把代码写这样,map('*2'.toFunction(),alist),因为全局用户级函数都会对应为函数的参数自动执行 toFunction(),只要 map('*2',alist) 就行了。另外,String.prototype 上还有 JavaScript-Like 的 call、apply 方法。
十、结语
functional.js 很强,很有用,很牛X;但同时也很年轻(7.20 发布),很多可以实现的功能还不完善,不说列表领悟什么的吧,至少应该把 Haskell Prelude 库在通用列表操作方面的函数的移植工作完成。我们期待 Oliver Steele 的表现。
July 14, 2007
新人报到!
原题是这样的:
一个六位数,分别用2,3,4,5,6乘它,得到的五个新数仍是由原数中的六个数字组成,只是位置不同,则此六位数是多少?
我把这题给做出来了!
def pcb(i):
if a(i)==a(i*2):
return pcb_3(i)
def pcb_3(i):
if a(i)==a(i*3):
return pcb_4(i)
def pcb_4(i):
if a(i)==a(i*4):
return pcb_5(i)
def pcb_5(i):
if a(i)==a(i*5):
return pcb_6(i)
def pcb_6(i):
if a(i)==a(i*6):
return i
def a(i):
c=b(i)
c.sort()
return c
def b(i):
return list(str(i))
# 这个原来我写的是错的,后来师父改滴——其实改滴也不好!
for i in range(100000,1000000):
if i and pcb(i):
print i
break
师父在学校那台破电脑上输代码滴时候抱怨说我的程序可以用程序写唠。这有什么关系,反正我的代码的速度超过师父的啦!
def list_equ (ls):
for cond in map(lambda x: ls[0] == x, ls):
if not cond:
return False
return True
def sort (ls):
ls.sort()
return ls
def test_num (num):
return list_equ(map(lambda x: reduce( \
lambda i,j: i+j, sort(list(str(x * num)))), range(2,7)))
def give_rst ():
return filter(test_num, range(100000,1000000))
for i in range(100000,100000):
if (test_num(i)):
print i
但是,最后师父使出了杀手锏,把这题用数学方法解出来唠,他个DB。。。
以后一定要超过他!
July 5, 2007
难得骂人
2007-07-05 12:26:51 gcd0318 (北京) 汇编是SB?这话谁说的?注意看原文,我对汇编的判断是:
|
| | 2007-07-05 13:21:48 氷の鋭 (南京) 扛不住楼上了,这么恶心的话说一遍不够再说一遍...我都厌恶地没法儿引用了,免得再污染一次别人。只有SB程序员才会以SB的思维一次只走一步地用汇编!你不是自称看过《计算机编程艺术》吗?
|
| | 2007-07-05 13:53:46 氷の鋭 (南京) 而且再说了,什么人才有资格批评汇编?Scheme 程序员;而 JavaScript 正是 Scheme 思想的实用化体现。LSS 一会儿说汇编 XX,一会儿又说 JavaScript 玩具,心想这人那叫一个牛啊,让人肃然起敬;弄了半天是一 C/C++ 程序员,你不觉得你批评汇编是在狗咬自己人吗?还看不起 ECMAScript 标准委员会 IEEE,一看,又肃然起敬,心想这牛人又创造了一打编程语言了;弄了半天是一搞自然语言处理的还看不起这个看不起那个,啊?楼上下不觉得这个笑话很冷 吗?!
|
June 25, 2007
各个语言编程的风格
续 Re: 各个语言社区的风格。
不和任何编程风格相绑定的语言是玩具,例如 Lisp/Scheme、Perl、JavaScript,就好比法杖,拿来敲人不行,但往往可以拿来放魔法;
和两种编程风格相绑定的语言是怪玩具,例如 E、Ruby,就好比竹剑,拿来劈人吧,勉强;拿来放魔法吧,凑合;
只和特定的一种编程风格相绑定的语言才是常规的武器,这你可能会想到很多,但也有些区别:
- 和一种与设计原意相背离的编程风格相绑定的语言是怪武器,例如 C++、就好比吴钩,杀人的动作既不是砍又不是刺,是拉,用起来很别扭;
- 和一种与设计原意相符合但是过时了的编程风格相绑定的语言是笨重的武器,例如 C、Java/.Net,就好比铡刀,那边要杀人了,非得把敌人逮着绑好了推到你手边才能动手,步骤繁琐;
- 和一种与设计原意相符合并且有理论支持的编程风格相绑定的语言才是好武器,例如 Haskell、ML/OCaml、Erlang、Python,就好比刀剑,拿起来就能用,用的时候不是砍就是刺,干净利索。
June 24, 2007
Re: 各个语言社区的风格
Smalltalk/Self:我们是设计飞机的。
Lua/JavaScript:我们是修理飞机的。
C/C++:我们是造飞机的。
Prolog/Planner:我们是开飞机的。
Python/Ruby:我们是卖飞机的。
Java/.Net:我们是打飞机的...
FP 做数学统计题
不用什么 GNUplot、Mathematica,俺用 Haskell,自己动手,丰衣足食!
一. 独立性检验
卡方就是一个公式:
kaf a b c d =
((sqr ((a * d) - (b * c))) * (a + b + c + d)) /
((a + b) * (c + d) * (a + c) * (d + b))
-- sqr 函数是用来求一个数的平方的,再实现一下:
sqr n = n * n
这样就搞定一种题了!以后算卡方不要太简单,把要检测独立性的四个数据按顺序作为 kaf 函数的参数输入 Hugs 就搞定了!
Main> kaf 184 61 91 9
11.097807845216 :: Double
二. 线性回归分析
其实就是求一个 y = a + bx 中的 a,b。把 xn 值、yn 值各表示为一个列表,像这样来使用:
Main> lrb [1..8] [5.54, 7.52, 10.02, 11.73, 15.69, 16.12, 16.98, 21.06]
2.12142857142857 :: Double
Main> lra [1..8] [5.54, 7.52, 10.02, 11.73, 15.69, 16.12, 16.98, 21.06]
3.53607142857143 :: Double
那么,就可以根据公式把它们抄出来:
lrb xl yl =
-- 这里要注意一下,数学中的 sigma 是一种记法而非函数
(sigma
(\(x, y) -> (x - xp) * (y - yp)) (zip xl yl)) /
sigma (\x -> sqr (x - xp)) xl
where
xp = avg xl
yp = avg yl
lra xl yl = avg yl - (lrb xl yl) * avg xl
其中,avg 函数用来求一个列表的平均值(这里也就是所有 x 取值的平均值啦),它需要知道列表长度:
avg ls = sum ls / len ls
len [] = 0
len (x:xl) = (len xl) + 1
最后一个 sigma 函数的实现也很简单,就是累加嘛,只是写地有些不好看罢了:
sigma f (x:xl) =
if xl == []
then f x
else f x + sigma f xl
重申一遍,这里的 sigma 被表示为函数而非数学形式。那么,猜猜下面的 sigma 的数学形式是什么?
Main> sigma (\(x, y) -> x * y) [(1,4), (5,3), (3,8), (2,4)]
51 :: Integer
三. 线性相关系数
线性相关系数 r 的求法也只是个函数,再抄一遍:
lrr xl yl =
(sigma
(\(x, y) -> (x - xp) * (y - yp)) (zip xl yl)) /
sqrt ((sigma (\x -> sqr (x - xp)) xl) *
(sigma (\y -> sqr (y - yp)) yl))
where
xp = avg xl
yp = avg yl
以后这样用就行了:
Main> lrr [1..8] [5.54, 7.52, 10.02, 11.73, 15.69, 16.12, 16.98, 21.06]
0.987345979074916 :: Double
哇!这组数据的线性相关性接近 1,好高啊!
四. 结语
现在发现 Haskell 还真是实用,这个周末数学作业不愁了,打魔兽去喽!
最后给楼下的看客留一个与 FP 没什么关系的小题目:
对于一组线性相关数据,往往要求它的所有产生数据—— a、b、r,还有线性回归函数,顺便还要用这个函数再求一下对未来的预期。我如果当真输入 3 遍数据然后手工计算预期岂不是很傻?!想想看怎么让我只输入一遍数据就得到所有的产生数据(设计一下数据结构而已)还有线性回归函数(其实只有这个才有那么一点点技术含量)。
June 12, 2007
胡侃:面向对象思想的进化
本文作者还是那个无名小辈 lichray。他在考查了一些语言和历史之后,觉得有必要谈一谈自己对面向对象思想的一些诡异的想法。文中会提到许多编程语言,不过当然了,重点在于思想,文章不是用来推销语言的。
面向对象编程思想的提出已经不是几年而是几十年了,考查其思想的变化,一方面是对现有语言的一些评判,另一方面,也算是对前辈计算机科学家的缅怀。 ——题记
Kristen Nygaard在1962年发明的 Simula 语言现在被认同为世界上第一种明确实现面向对象编程中某些“必要”元素(比如 class)的语言。Simula 是从 Algol 发展来的,可以说,是一种增加了 class 这个数据类型的 Algol,并将参数传递的默认模式从“按名调用”换成了“按引用调用”,还提出了根据类型确定初始化过程的方法。
从 Simula 的时代开始,科学家们在解决软件复杂度方面的思路开始“异常开阔”——当然也有资深的老派牛人们不这么认为。比方说 Peter Norvig,写了[url="http://norvig.com/design-patterns/"]Design Patterns in Dynamic Programming[/url]一书来反驳。他认为设计模式早已体现在以 Lisp 为首的一批语言中了,根本不需要什么面向对象。但我还是跟从偶像 Alan Kay 的观点。某些时候,把一种特定的编程风格做进语言里也是必要的。
言归正传。Simula 之后的语言开始试图把 class 作为编程的基础,并提出了一系列出色的、基于类的编程风格,最终确定了“万物皆对象”这一面向对象理论的“终极思想”。于是,Alan Kay 的 Smalltalk 在 70年代诞生了。它是世界上第一个真正把面向对象作为程序组织基础手段的编程语言。它首次明确实现了“消息”和“继承”这两个重要概念,对于“封装”和“多态”也给出了里程碑式的解决方案。在 Smalltalk 程序中,一切程序元素(除了词法元素)都是对象,一切操作都是消息。而且,Smalltalk 的实现本身就是用 Smalltalk 写成的,这也就意味着,对于一个 Smalltalk 程序来说,它的底层也是以面向对象为基础的。直到现在,也只有 Self 语言有资格在面向对象的程度方面与 Smalltalk 一较高下。
Smalltalk 在成为里程碑的同时,也成为了分水岭。Smalltalk 之后的面向对象语言的发展方向分成了两条路:一是继续把 Smalltalk 中封装的概念扩大化,为程序员添置更多的约束,以支持所谓的“大规模开发”的需要;另一方面,一些人主张继续理解面向对象的原理,为这一思想添加数学上解释,而不仅仅是“拿来主义”,使程序员更不易出错或“分神”。
正像大家所猜测的那样,前者指的是以 C++ 为代表的、坚持以命令式语言风格去实现面向对象的“有类”语言,后者是以 Self 为代表的、准备以函数式语言风格去理解面向对象的“无类”语言。
有类语言中,C++ 是曾经的“蔚然大宗”。它最初的名字是 C with class,很显然,一开始只是想对 C 作出像 Simula 之于 Algol 那样的修改。但是,C++ 诞生的时候(1983年),也是 Smalltalk 80 产生的时代,也是 Ada 语言正在开发的时代。在享受了 Smalltalk 提出的类方法、对象方法、私有方法等等语言设施之后,C++ 提出了3个为后来的有类语言广泛采用的关键字:private,public,protected。不避讳的话,可以简称3P。这种区分相对于 Smalltalk 来说略少了一点,但事实证明,在实际的抽象中是够用的。另外,C++ 还从 Algol68 (这只是一个失败的旧事物罢了)那儿学来了运算符重载,还有 Ada 中的泛型和程序包。堆砌了太多特性的 C++ 显得有些不堪重负,而且失败的教育(总是拿它当 C 的升级版讲解!)、失败的实现(尤以 Visual C++ 为甚)也是它后来在开源界不那么受欢迎的重要原因。C++ 之后的有类语言开始了消减多余特性,理清思路的努力。
紧接着 C++ 之后出现的语言主要有 Objective Pascal 和 Objective-C。后者是 Mac 公司的拳头开发语言,前者是什么?就是 Turbo Pacal 中的 Pascal 语言。二者试着给古老的语言们添加新的元素。前者只是很客气的添加了 new 和 unit,特性不足;后者的转型是成功的,但说句题外话:想象一下 C 的基础上直接加上 Smalltalk 的消息传递语法,这就是 Objective-C 的语法设计。
Java,原名 Oak。这个名字现在已经处于“无敌”状态,去年已经在语言排行榜上 K 掉 C 当上了老大。Java 没有什么新东西,正如上文所说,只是一个消除了多余特性后的产物。成功的原因,一是时代的召唤,而是官方实现的基于虚拟机的跨平台特性。虚拟机早已不是什么新事物了,不过有大公司出来宣传确实是新鲜事。也许是受了微软的影响,大多数普通程序员更愿意那种喜欢被某个大公司“罩着”的感觉,于是纷纷用起了 Java。历史就是这样,听不到你的嗟呀。
Java 火起来了,各大计算机方面的媒体上充斥着 Java、OOP 等等的字眼,仿佛面向对象只剩下 Java 一种模式。各种语言纷纷改头换面,以适应“新时代”的要求:Perl 加上了4P(package 也算一个);Objective Pascal 摇身一变成了 Boject Pascal,再改名 Delphi;Basic 名字前加上了 Visual,后来后面又加了 .Net;C++ 把 ++ 改成了 # 号;就连一向对面向对象嗤之以鼻的 PHP 都被迫升到了 4.0、5.0;我的“母语”Action Script”版本也升到了2.0、3.0。
在有些人眼中,这是一场“意义重大”的变革;但在我眼中,这是一场灾难。
因为,在某个角落,无类语言们正在为寻找面向对象的数学理论基础而努力。为什么要寻找数学理论基础?这个问题早在70吗=年代就已经不是个问题了,而现在居然还有人不明所以。有类语言和无类语言的区别,就像是命令式与函数式之间的区别,就像是图灵机和 Lambda 演算之间的区别,就是一字一次的编程和面向整体操作的区别,就是 Bug 多和少之间的区别,就是单线程与高效并发之间的区别,就是必将灭亡的旧事物和必将走向辉煌的新事物之间的区别……这么多,还不够吗?
无类语言从已有的 lambda 演算理论中寻找适合解释面向对象思想的部分。1986 年研究完成的 Self 语言首先抛弃了 class 关键字,从对类和对象这两个基本问题上做文章。“万物皆对象”,类就是对象,但用它可以产生对象;怎么产生?通过复制已有对象产生。这种手段称为“基于原型的面向对象程序设计”。Self 之于无类语言,相对于 C++ 之于有类语言。它是无类语言中的蔚然大宗。但可惜的是,Sun 公司的 Self 实现几乎没有进步;Smalltalk 可能因为受够了 Java 之流自称继承了自己,认为 Self 是它唯一的“知心朋友”,发起了一个叫做 Morphic 新体系,算是对经典的延续。
Self 之后,1993年出现了 Lua。Lua 是无类语言的一个特别“函数式”的版本,在继承了 Scheme 所有思想之后,消灭了专有 List 这一数据结构,全以哈希表代之,并出色地解释了许多程序表示上的一些不太明朗的问题,还区分了消息发送和普通函数调用。Lua 不论从那个角度(科学或者商业)来看都是成功的,是无类语言中的佼佼者。
Lua 之后出现了 JavaScript,原名 LiveScript(1995),国际标准收录名为 ECMAScript。JavaScript 与 Java 诞生于同一时代,生不逢时的同时又生而逢时。说它生不逢时,是因为在 Java 的盛名之下,不负众多程序员的“众望”,被他们指责为“假面向对象”;说它生而逢时,是因为,它总算没在一浪高过一浪的 class + 4P 的嚷嚷中倒下,成为我们最为熟知无类语言。它实在是太优秀然而又太谦虚了:它秉承了 Lisp 家族的一贯传统,能够用数据表示程序本身(JSO);它又有足够的函数式编程特性,但它谦虚地称之为 function;它用复制对象再用 new 关键字 apply 构造函数的方式漂亮地解释了类和对象的关系;相对于 Lua 取消了消息发送和函数调用之间的界限;在没有任何 4P 关键字的情况下还能用逃逸变量理论实现 4P 的所有特性(不要被 dojo 之类的库蒙蔽了双眼)。几乎是一个完美的 Self 的继任者,但还是那句话,真正会 JavaScript 的人不多啊。
至此,无类语言的发展似乎已经很令人满意了,理论似乎已经成熟,只剩下一个问题:函数本身是一个什么样的对象?E 语言(1987,提出很早,实现太晚)回答了这个问题。答案很简单。函数是对象的一种,对象和方法都是 lambda 算子,如果你愿意,又可以把函数的函数体再表示为对象的一个方法!看起来这个答案不那么起眼,但它确实让人们领教了 lambda 演算在抽象层次上的嚣张。同时,E 语言还大胆地加入了对象继承的概念——一个对象可以像类继承一样继承其它对象的属性——因为这仅仅是 lambda 算子的扩展罢了!
但是,无类语言的进步不会就此结束,还有更多、跟有力量的无类语言产生,比如 Scala,一个给无类语言添加了 ML 中的静态多态类型判定和惰性运算的变态……
历史就是这样一个聋子,听不到你的嗟呀。不过,但愿后人会从这些嗟呀中借鉴到很多。
注:还有两个很牛语言没提到:Ruby 和 Python。其中前者学 Smalltalk 学地精神分裂,去掉了 Smalltalk 的强大 IDE 之后居然一炮走红;Python 是个不知道该站在那一边的语言,既没有 3P 又知道逃逸变量或 block 是什么,惨兮兮的。
June 8, 2007
Haskell:函数 or 数据
haskell里面是不是所有的函数都是lambda实现的?不然为什么 :t 操作符所返回的都是 lambda的表示,也就是说都是用lambda解释的.
呵呵,最好讲下haskell里的type.和java或c里面的不同.
是。只是语法上不太像。也可以说不是,因为那是lambda的"1.5版"(我的“术语”,Scheme中的是1.0版,学名“一阶lambda演算”) ——Curry化算子。Curry化算子认为,一切都是函数(包括普通数据),但如果这个数据的产生式中每一项都是严格的(比如 num = 4 + 3, 3、4都是可以直接推导而不需要进行惰性化的),那么这个数据可以免除其作为函数的义务。对于产生式中有需要推导的数据,Haskell仍将其表示为普通 数据(因为类型可以直接推导来确定。只要产生它的表达式中函数调用存在,那么就会被认作函数;但是它的类型只需要一步即可推导,所以在Haskell中,它“看上去”是普通数据。)。
fact 0 = 1
fact n = n * fact (n-1) -- 这是函数,要推导
num = 5 + fact 4 -- 这个是事实上是函数,但类型上看不出来
另外,之所以haskell中的函数全是lambda的表示,和它的类型系统也有关系。前面说了,Haskell中对函数的理解已经超越了数学映 射1.5倍,重点在于对参数列表的理解。数学映射和一阶lambda都认为参数是连续的、一次传递一批,就像Scheme中那样:
(function arg1 arg2)
但Curry化算子认为参数是不连续的、可以分段传递(前提是个数事先固定),每当传递结束而参数不够是,自动转换为一个期待余下参数的函数。这就意味着,如果用Scheme的语法来说明Curry化算子就成了这样:
((+ 1) 2) ;调用一个固定参数个数为两个的函数
这句代码都将行得通!
(map (+ 1) '(1 4 2 5 2))
这对于Scheme来说是非常荒唐的(用宏另当别论),但对于Haskell这是基础:
map (+1) [1, 4, 2, 5, 2]
所以,Haskell的类型系统中对于函数类型的表示才会是那么多个箭头:因为,只要需要推导,就有函数:
Main> :t (+1)
flip (+) 1 :: Num a => a -> a
看出来了吗?不连续的类型导出过程,和java/c等等等等最大的不同点。
至于Haskell类型系统的核心力量——静态多态类型推断,讲起来是在很麻烦。去看中文版的ML语言的书里讲的很详细。
OOP 诡异教程(上)
- 本文分上、下两篇,站在一个难以名状的角度上研究了 JavaScript 语言中面向对象机制的起源、内涵和发展,带领读者从原始森林走向高楼大厦。文章作者 lichray 是个 ECMAScript 的狂热追随者,mozilla.org 邮件列表里的无名潜水员。
- 文章中使用了 Rhino 解释器,行开头有 "js>" 表示那是输入,输入下一行没有这个标记的表示解释器回馈消息。
- PS: 读懂本文需要对 JavaScript 闭包和逃逸变量有较深入的了解。
一. 对象和消息
考虑一下我们平常怎么说话的。我们叫某某人做某事,用下面的句式:
forest run!
其中"!"是语气的标志,对于编程语言来说是没有意义的,全部换成".":
forrest run.
不知道如果我告诉大家上面这句话就是 Smalltalk 语言中一个合法语句大家会怎么想。好了,不谈这个。这样我们就得到了一种语法,"宾"谓结构:
ObjectVerb ::
Object Verb.
如果让它支持多个 Verb,比如
forrest run, jump, stop.
可以扩展成这样:
ObjectVerb ::
Object VerbList.
VerbList ::
Verb
Verb , VerbList
很明显,对于 JavaScript 来说,上面的 BNF 不可能和任何一个产生式匹配。问题出在哪儿?我们要帮 JavaScript 指定,谁是 Object,谁是 Verb。鉴于 Object 只有一个,Verb 有多个,我们可以用括号来区分它们,然后把最后那个句号去掉:
ObjectVerb ::
Object ( VerbList )
这样上面的那句话就变成了下面的形式:
forrest (run, jump, stop)
很像函数调用,是吧?不过还有一个问题,现在这些 Verb(s) 对于 JavaScript 来说是“裸词”(Perl 语),我们可以避开再去定义这些标识符,用字符串代替;最后再说明一下 Object 是什么:
forrest ('run', 'jump', 'stop')
那么现在我们第一个“模仿”自然语言的程序版本出现了,加上下面针对 JavaScript 的文法:
Object ::
Identifier
Verb ::
StringLiteral
二. 实现消息传递
有了文法,一切都好办。看得出来,我们下面的工作是定义能创建一个新 Object 的函数,函数中有一些动作,产生的新 Object 是一个能处理这些消息的函数。创建 Forrest Gump 的函数还可以创建 Tom,Mike 等等;他们都是 People:
function People () {
function run () {
print("I'm running!")
}
function jump () {
print("I'm jumping!")
}
function stop () {
print("I can't stop!")
}
return (function (verb) {
switch (verb) {
case 'run': run(); break
case 'jump': jump() ;break
case 'stop': stop() ;break
}
})
}
为了简单起见还可以把返回的那个函数写成这样:
(function (verb) {
eval(verb)();
}
})
Ok。现在我们来试一试这个智商低于 85 的 Forrest Gump 怎么样:
js> forrest = People()
js> forrest('run')
I'm running!
js> forrest('jump')
I'm jumping!
js> forrest('stop')
I can't stop!
事情就是这样。我们成功地创造了对象,还让他做动作、说话。
不过,这个实现并不是我们上文中最后一个文法所指出的。它不支持连续发送指令。改一改。要加入顺序执行指令的办法:
function People () {
function run () {
print("I'm running!")
}
function jump () {
print("I'm jumping!")
}
function stop () {
print("I can't stop!")
}
function _do_verbs_ (verblist) {
for (var i=0; i <> forrest = People()
js> forrest('jump','run','jump','stop')
I'm jumping!
I'm running!
I'm jumping!
I can't stop!
三. 利用消息传递处理状态
什么是状态?我们在进行面向对象编程时,把状态表示为对象的一组数据,我们称之为“属性(property)”。在我们的消息传递编程风格中,可以直接把这些数据堆到产生对象的那个函数中去。下面给 Forrest 加入一个状态,Forrest 口袋里的钱。先得声明原先有多少钱:
forrest = People(1000)
然后,我们希望可以执行这样的代码,让 forrest 支出 200 美元:
forrest('pay', 200)
但很明显,我们无法分清 200 是 Verb 还是 'pay' 所要求的数据。我们只得简化文法,只允许一次发送一个消息,以保全我们的脑细胞:
forrest('pay')(200)
也就是说,我们需要让 forrest('pay') 这一表达式返回一个能改变状态的函数,而不仅仅是调用函数来显示一句话。也就是说,如果我们想让 Forrest 急得跳起来,我们先得跳起来:
forrest('jump')()
新时代的 Forrest 实现如下(省略了一点多余的代码):
function People (money) {
//var money = money
function pay (dollars) {
money -= dollars
}
function restMoney () {
return money
}
function run () {
print("I'm running!")
}
return (function (verb) {
return eval(verb)
})
}
试一下。先支出 200 美元,然后看看他还剩多少钱:
js> forrest=People(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800
当然,我们的 Forrest 还可以赚钱。下面这个版本比较彻底地说明了消息传递编程风格的一切。可以直接修改钱之后,我们可以不需要在创建 Object 的时候就说明原有多少钱;当然,使用注释中的版本更自然:
function People (/* money */) {
var money = 0; // var money = money ? money : 0;
function setMoney (dollars) {
money = dollars
}
function addMoney (dollars) {
money += dollars
}
function pay (dollars) {
money -= dollars
}
function restMoney () {
return money
}
return (function (verb) {
return eval(verb)
})
}
试一下吧:
js> forrest = People()
js> forrest('addMoney')(1000)
js> forrest('restMoney')()
1000
js> forrest('pay')(200)
js> forrest('restMoney')()
800
上篇完。小结一下:消息传递的编程风格指的是,把函数 A 的执行上下文当作对象的数据环境,在此定义对象的动词(函数),然后从此上下文中返回一个可以接受、处理消息的函数(常为匿名)。用函数 A 产生消息处理器作为对象,向此对象传递参数作为消息,以此执行函数 A 环境中定义的动作,这些动作还可能改变所在上下文中用一组数据定义的对象状态。