newLISP你也行 --- 流

2012-04-06 00:21

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
  ;