以Common Lisp程序为例。
一个Common Lisp程序本来可以只是一个函数,但为了便于人类进行阅读,故将一个函数拆分为多个小函数,至于模块化重用性易调试,实乃意想不到的收获。如果将整个程序只写在一个函数里,因为用于显示程序的屏幕是有限的,肉眼的视角是有限的,肉眼一次性能捕捉到的内容将非常受限,那么每次移动屏幕和视觉所获取的信息都会大打折扣。物理上屏幕和生理上视角的双重局限,压迫人类进行手动拆分函数过程,然究其根本,实为“渺沧海之一粟,寄蜉蝣于天地”的无奈。小小的人,却欲观尽浩渺苍穹,茫茫千万年,前赴后继,何其悲壮。也许世界的全部,就是人类自身,就是万千众生,曰“一花一世界”,不正如是?
函数"string-to-html",目的是将大段的文字转换成符合"HTML"语法的文字,如果是一段文字就添加"<p></p>"标签,如果是空行,就添加"<p><br/></p>"标签。
例如,给定大段文字:
(defparameter *string-to-html*
"0
1")
需要转换成:
"<p>0</p><p><br/></p><p>1</p>"
不把函数分拆时,把所有操作都包含在一个函数里:
(defun string-to-html (string)
(format nil "~{<p>~A</p>~}"
(mapcar (lambda (i)
(if (or (null i)
(and (stringp i)
(= (length i)
0)))
"<br/>"
i))
(let ((var nil)
(stream (make-string-input-stream string)))
(do ((line (read-line stream nil 'eof)
(read-line stream nil 'eof)))
((eql line 'eof))
(push line var))
(reverse var)))))
调用:
(string-to-html *string-to-html*)
"<p>0</p><p><br/></p><p>1</p>"
拆分后的全局函数:
(defun null-string-p (string)
(or (null string)
(and (stringp string)
(= (length string)
0))))
(defun %%string-to-html-1 (string)
(let ((var nil)
(stream (make-string-input-stream string)))
(do ((line (read-line stream nil 'eof)
(read-line stream nil 'eof)))
((eql line 'eof))
(push line var))
(reverse var)))
(defun %string-to-html-1 (%%string-to-html-1)
(mapcar (lambda (i)
(if (null-string-p i)
"<br/>"
i))
%%string-to-html-1))
(defun string-to-html-1 (%string-to-html-1)
(format nil "~{<p>~A</p>~}"
%string-to-html-1))
调用时:
(string-to-html-1
(%string-to-html-1
(%%string-to-html-1 *string-to-html*)))
"<p>0</p><p><br/></p><p>1</p>"
拆分后的局部函数:
(defun string-to-html-2 (string)
(labels ((null-string-p (string)
(or (null string)
(and (stringp string)
(= (length string)
0))))
(%%string-to-html-2 (string)
(let ((var nil)
(stream (make-string-input-stream string)))
(do ((line (read-line stream nil 'eof)
(read-line stream nil 'eof)))
((eql line 'eof))
(push line var))
(reverse var)))
(%string-to-html-2 (%%string-to-html-2)
(mapcar (lambda (i)
(if (null-string-p i)
"<br/>"
i))
%%string-to-html-2)))
(format nil "~{<p>~A</p>~}"
(%string-to-html-2
(%%string-to-html-2 string)))))
对比上述三种实现方式,分拆成全局函数时:
- 成本
- 需要输入更多的字符和行数,曰物理成本:(90-54)/54=2/3
- 不分拆函数时字符:Region has 16 lines, 54 words, and 661
characters. - 分拆后的局部函数:Region has 23 lines, 90 words, and 899
characters. - 分拆后的全局函数:Region has 25 lines, 90 words, and 673
characters.
- 不分拆函数时字符:Region has 16 lines, 54 words, and 661
- 需要为多个小函数选择合适的名字,曰精神成本:用一个名字抽象化一个过程
- 需结合函数的功能
- 需调试时能见函数名就知功能
- 需要输入更多的字符和行数,曰物理成本:(90-54)/54=2/3
- 效益
- 易阅读:小块内容,缩进量少,显而易见的函数功能。
- 模块化:使用返回值时只用确保参数符合要求,自然就返回合适的值。
- 重用性:直接分离出某个小函数,用于别的项目。
- 易调试:函数出错时容易定位到某个小函数 -
从上述的数据里可见,为了所谓的效益,人类宁可多打点字,尽管要增加2/3的打字量,宁可想个好名字,尽管要多费点心神去打磨。
赞美函数式编程本身就像宣扬人类有多么无奈、弱小一样,生怕强大的宇宙不知道人类有多么菜,但这确确实实是弱小人类对抗浩瀚宇宙的有效举措,再说了,任何函数式的程序天生就有拿来即用的禀赋,于人于己都有益处,多费时间就多费时间吧,人类在抽象这条路上算是越走越远了。