newLISP你也行 --- 流
by address-withheld@my.opera.com.invalid (F0)
at 2012-04-05 16:21:19
original http://my.opera.com/freewinger/blog/show.dml/44440462
#############################################################################
# Name:newLISP你也行 --- 流
# Author:黄登(winger)
# Project:http://code.google.com/p/newlisp-you-can-do
# Gtalk:free.winger@gmail.com
# Gtalk-Group:zen0code@appspot.com
# Blog:http://my.opera.com/freewinger/blog/
# QQ-Group:31138659
# 大道至简 -- newLISP
#
# Copyright 2012 黄登(winger) All rights reserved.
# Permission is granted to copy, distribute and/or
# modify this document under the terms of the GNU Free Documentation License,
# Version 1.2 or any later version published by the Free Software Foundation;
# with no Invariant Sections, no Front-Cover Texts,and no Back-Cover Texts.
#############################################################################
浩瀚环宇,星辰流彻,冥冥之中,自有规律.
这一节,我们要学会如何在 newLISP 中控制代码运行轨迹.
(keyword expression1 expression2 expression3 ...)
这就是所有流控制语句的原型.
根据keyword计算的不同,执行后面不同的表达式.一切还是list.
一. 判断:if...
先让我们看个简单的判断.
(if 饿了? (吃饭) )
;(if hunger? (eat))
如果 饿了? 这个symbol计算后的结果是 true ,那就执行第三个元素, (吃饭) .
这里得注意一点,newLISP 里各位是可以完美使用中文的,不管是函数还是symbol.你想把
所有的代码写成中文的也行,不过不利于和国外的同学交流.在这里还有个约定俗成的写法
就是,如果某个是判断true false 的,在定义这个函数的时候,就会在函数后面加个 ? 号.
下面就是个导弹发射的案例.
(if x 1)
; 如果 x 是 true 列表返回 1
(if 1 (launch-missile))
; missiles 导弹 are launched 发射, 因为 1 是 true
(if 0 (launch-missile))
; missiles 导弹 are launched 发射, 因为 0 也是 true
(if nil (launch-missile))
;-> nil, 这次无法发射了, 因为 nil 是 false
(if '() (launch-missile))
;-> 还是无法发射 因为 () 也是false
我们可以使用任何的表达式作为判断条件.
(if (> 4 3) (launch-missile))
;-> 4 > 3 计算的结果是true , 所以 导弹被发射了
(if (> 4 3) (println "4 is bigger than 3"))
"4 is bigger than 3"
这里大家也许会疑惑,什么是 true 呢.
true 就是除了 nil 和空列表 () 外的任何值.
那什么是 nil 呢.
就是那些不存在的或者未被赋值的,或者被判断为错误的(比如2 小于 1).
可以用nil? 这个函数来判断,某个元素是否是 nil .
同样 true? 可以判断某个元素是否是 true .
但是 nl里头没有 false这个关键字.
>(true? -1)
true
>(nil? (< 3 2))
true
;因为3 小于2 是 错误的 所以 (< 3 2 ) 返回了nil 所以nil? 判断 这个元素是nil
;nil? 只是用来判断后面的元素是否是nil ,这里是,那当然要返回true了
;你可以把true理解为真 除了true外 其他都不是true.好像很废话-!-
(if snark (launch-missile))
;-> nil ; 发射失败,因为 snark 这个symbol没有赋值
(if boojum (launch-missile))
;-> nil ; 同样失败 因为boojum也没有值
(if false (launch-missile))
;-> nil ; 一样 false 也没听说过
有花不见叶,叶生不见花,生生世世,花叶两相错。--- 彼岸花
现在我们给if 加上第三个表达式.
在文章里你可以把表达式,元素这些东西看成一个东西:数据.在他们要计算的时候,我
会用表达式表示,在他们不需要计算的时候我用元素表示.但是计算的目的是为了得到数据
,所以表达式和元素之间是可以互相替换的.All Is Data.
(if x 1 2)
; 如果 x 为 true, 返回 1, 否则返回 2
- (if 1
(launch-missile)
(cancel-alert))
; 导弹发射成功
- (if nil
(launch-missile)
(cancel-alert))
; 导弹发射不成功 ,跳出终止警告.
- (if false
(launch-missile)
(cancel-alert))
; 导弹发射不成功 ,跳出终止警告.
下面的是是在平常编码中经常用到一种形式
- (if (and socket (net-confirm-request)) ;判断socket是否成功,request是否符合请求
(net-flush) ; 如果2个条件都符合 执行 (net-flush)
(finish "could not connect")) ; 如果有一个条件不符合,发出错误提示
人生的每一条路都是自己选得,不幸的是你必须选,幸运的是你有千万条路可以选.
if 同样可以有N个判断-执行语句.
只要判断为真则执行后面的语句,然后返回.
- (if
(< x 0) (define a "impossible")
(< x 10) (define a "small")
(< x 20) (define a "medium")
(>= x 20) (define a "large")
)
这和传统LISP中的cond很像,但是少了括号.更加的灵活,这也是nl的一个特点,更随意
,更方便.
如果你在条件判断为true 后,想执行多个语句怎么办?
一种方法是使用when
- (when (> x 0)
(define a "positive")
(define b "not zero")
(define c "not negative"))
还有种方法就是使用代码块Blocks.下面马上就会说.
- (if
(< x 0) (begin (define a "impossible") (define b "OMG"))
(< x 10) (define a "small")
(< x 20) (define a "medium")
(>= x 20) (define a "large")
)
;如果x 小于 0 就同时定义了 a 和 b 两个symbol.
之前我们说过nl会把列表中的第一个元素作为函数.如果他的第一个元素是列表,那nl
就会先计算这个列表,然后把返回值作为函数应用到后面的元素中.
(define x 1)
((if (< x 5) + ) 3 4) ; 第一个列表用来抉择是用 + 还是 ?
7 ; 这里执行的是 +
内嵌列表 (if (< x 5) + ) 通过 (< x 5) 的值来决定是返回+ 还是 .这一切都
得由 x 的值来决定.
(define x 10)
;-> 10
((if (< x 5) + ) 3 4)
12 ; 执行了 * 乘法操作
因为我们可以动态的决定使用什么函数,所以在写出的代码就会灵活很多.
(if (< x 5) (+ 3 4) ( 3 4))
可以写成这样:
((if (< x 5) + ) 3 4)
整个过程类似下面:
;-> ((if (< x 5) + ) 3 4)
;-> ((if true + ) 3 4)
;-> (+ 3 4)
;-> 7
在nl里,任何的表达式都是会返回值的.包括if 表达式.
(define x (if flag 1 -1)) ; x is 是 1 或者 -1
;(set 'x (if flag 1 -1)) ;我在赋值symbol的时候一般用set,定义函数的时用define
- (define result
- (if
(< x 0) "impossible"
(< x 10) "small"
(< x 20) "medium"
"large"))
result的值依赖于x的值.如果x大于等于20将会返回 "large".如果没有 "large" 这
个表达式,而x又大于20呢?自己测试下吧~~~~ 想知道为什么可以查手册.
二. 循环Looping
当你需要重复劳动的时候,就会用到循环,比如下面这些情况:
操作列表中的每一个元素
on every item in a list
操作字符串中的每一个元素
on every item in a string
重复执行某个操作一定的次数
a certain number of times
不断执行操作直到某个特定的情况发生才停止
until something happens
当某个特定的条件为真时就一直执行操作
while some condition prevails
1: 遍历列表
Working through a list
每一个nl程序员都爱 list ,而dolist则可以让我们遍历操作每一个列表元素.
>(sequence -5 5)
(-5 -4 -3 -2 -1 0 1 2 3 4 5)
;sequence 产生一个了一个从 -5 到 5 的列表
下面我们就用 dolist 来遍历这个列表.
(define counter 1) ;定义一个symbol,用来记录遍历到了第几个元素
- (dolist (i (sequence -5 5))
(println "Element " counter ": " i)
(inc counter)) ; 遍历一个元素就加1
;输出结果
Element 1: -5
Element 2: -4
Element 3: -3
Element 4: -2
Element 5: -1
Element 6: 0
Element 7: 1
Element 8: 2
Element 9: 3
Element 10: 4
Element 11: 5
12
;在dolist最后一句执行的 (inc counter),作为dolist的返回值.
让我们看看 dolist 的语法.(help)是个宏,如果你下载了我的scite4newlisp,就会在
init.lsp看到他的源码.当然在scite里输入dolist,然后按空格也会弹出提示语法.
> (help dolist)
syntax: (dolist (<sym> <list> [<exp-break>]) <body>)
syntax中,只用 <> 括起来的是必选参数,最外层用中括号 [] 括起来的,是可选参数.
上面的<sym> 就是一个必须提供的临时 symbol.这个symbol的作用范围只在(dolist )内,
出了(dolist )没有任何作用.<list> 就是我们需要遍历的列表. <body> 就是我们要对列
表元素进行的操作. i 作为一个临时变量,每次遍历都加载不同的元素, 我们每一次遍历
的操作就是,打印遍历次数和元素值.和 if 不同, dolist 可以执行很多条语句,上面我们
既打印又增加counter的值.
上面我们用自己定义的 counter 存储系统的遍历次数,其实nl内部已经提供了一个变
$idx 用来存储循环的次数.
- (dolist (i (sequence -5 5))
(println "Element " $idx ": " i))
Element 0: -5
Element 1: -4
Element 2: -3
Element 3: -2
Element 4: -1
Element 5: 0
Element 6: 1
Element 7: 2
Element 8: 3
Element 9: 4
Element 10: 5
5
;println 的最后一个参数作为返回值
还有了一个强力的函数 map ,也可以遍历操作整个列表.不过他会将每一个列表元素
的操作结果,组装成一个新的列表,并返回他.
(map (fn (x) ( x 2)) (sequence -5 5))
;(map (lambda (x) ( x 2)) (sequence -5 5))
(-10 -8 -6 -4 -2 0 2 4 6 8 10)
fn是lambda的缩写,lambda用来创建匿名函数.map 将第二个参数作为一个函数,遍历
后面的元素.(fn (x) ( x 2))的作用就是将每一个列表值乘以2. map 将这些翻倍后的值
再组装起来返回给我们.
(define counter 1)
;效果一样自己测试下输出吧
- (map (fn (i)
(println "Element " counter ": " i)
(inc counter))
(sequence -5 5))
再介绍个很实用的函数 flat.他的作用就是把嵌套列表 "抚平" 成一个顶级列表.
> (flat '((1 2 3) (4 5 6)))
(1 2 3 4 5 6)
这样你就不用专门写个遍历函数遍历每一个嵌套列表了.记住他的效率比传统的递归
手工car,cdr递归和尾递归,快很多.速度是简洁是newLISP的两大特色.所以不用担心内部
函数的效率.
2: 遍历字符串
Working through a string
可以说在某些时候字符串用的比list还多,比如console里输出的全是字符串.在你刚
开始起步的时候,大部分时间都在接触字符串.当然nl提供了非常多的字符操作函数,这些
函数同时也能操作list.
(define alphabet "abcdefghijklmnopqrstuvwxyz")
- (dostring (letter alphabet)
(print letter { }))
;遍历字符串,输出每个英文字母的ascii码.
;{ } 相当于" " ,他们都是字符串的标志.
97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116
117 118 119 120 121 122
3: 完成特定次数的操作
A certain number of times
如果你想重复执行某个操作指定次数,可以使用 dotimes 或者 for .
>(help dotimes)
(dotimes (<sym-var> <int-count> [<exp-break>]) <body>)
和 dolist 一样需要提供一个临时变量<sym-var>,还有就是执行的次数<int-count>
.<body>是需要执行的语句,可以多条.
- (dotimes (c 10)
(println c " times 3 is " ( c 3)))
0 times 3 is 0
1 times 3 is 3
2 times 3 is 6
3 times 3 is 9
4 times 3 is 12
5 times 3 is 15
6 times 3 is 18
7 times 3 is 21
8 times 3 is 24
9 times 3 is 27
c作为计数值从0-9,这是nl里习惯.没学过编程的人可能不太习惯.那不妨这个看成你
的生日.如果一个人是1987年6月1日出生的.有人会在出生这天过1岁生日吗,肯定是要到
1988年6月1日再过是吧.那1987年这年只能叫0岁.当然这只是个比喻,方便你记忆.
还有[<exp-break>]我们没用到,很多循环函数里都有这个可选参数,这个参数的作用
就是,如果特定条件达成,循环则提前结束.比如:
- (dotimes (c 10 (= c 4))
(println c " times 3 is " ( c 3)))
0 times 3 is 0
1 times 3 is 3
2 times 3 is 6
3 times 3 is 9
true
计数器到了4 就退出不再继续执行下去.
dotimes 很方便,但是 for 更强大.因为 for 可以更细微的控制,开始值,结束值,和
递进值.
- (for (c 1 -1 .5)
(println c))
1
0.5
0
-0.5
-1
;