newLISP你也行 --- 流

  #############################################################################
  # 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
  ;从1 到 -1 每次减少0.5
 
  ;同样我们也可以让计数值从1 开始
  (for (x 1 10) (println x))
  1
  ...
  10
 
  ;dotimes则没这么灵活了
  (dotimes (x 10) (println x))
  0
  ...
  9
 
  >(help for)
  syntax: (for (<sym> <num-from> <num-to> [<num-step> [<exp-break>]]) <body>)
 
 
 
  4:  可选的结束判断表达式
      An escape route is available
 
 
      之前我们介绍的三个循环函数for , dotimes , dolist 都是在遍历完列表或者,执行
  完预订好的次数后才结束循环.那如果我们要提前结束,怎么办呢?这时候就可以用到上面
  提到过的[<exp-break>].
 
 
  (define number-list '(100 300 500 701 900 1100 1300 1500))
  ; 第一个版本
- (dolist (n number-list)
      (println (/ n 2)))
  50
  150
  250
  350
  450
  550
  650
  750
 
  ; 第二个版本
- (dolist (n number-list (!= (mod n 2) 0)) ; escape if true
      (println (/ n 2)))
  50
  150
  250
 
 
      第二个版本中,我们加了一个判断条件,如果遇到了一个奇数,就提前结束.
 
      提前结束的方法不止一种,还有一种会经常用到的就是 catch throw .
 
- (catch
-     (for (i 0 9)
          (if (= i 5) (throw (string "i was " i)))
          (print i " ")))
 
  ;输出结果
  0 1 2 3 4
 
      将整个循环表达式用catch 包起来,在内部需要跳出的地方加个判断,条件处理后用
  throw 跳出来.具体细节后面的课上会将.
 
 
 
 
  5.  直到某些情况发生,或者某些情况为真.
      Until something happens, or while something is true
 
 
      直到某种情况发生才停止工作,用 until 或者 do-until .
 
 
- (until (disk-full?)
      (println "Adding another file")
      (add-file)
      (inc count))
 
- (do-until (disk-full?)
      (println "Adding another file")
      (add-file)
      (inc count))
 
 
      两个函数的差别是, until 先判断条件是否成立,如果成立了为 true ,那下面的事情
  就不能做了,直接退出. do-until 就横了点,先进行一次操作,再判断条件是否成立.所以
  do-until 至少会进行一次操作.从上面的情况中,如果disk-full 磁盘满了,那就无法添加
  文件到磁盘里了,否则就会报错,所以上面用 until 更合适.
 
      而while 和until 则刚好相反,他是如果条件为 true ,则一直执行,直到体检不为
  true 才退出循环.
 
- (while (disk-has-space)
      (println "Adding another file")
      (add-file)
      (inc count))
 
- (do-while (disk-has-space)
      (println "Adding another file")
      (add-file)
      (inc count))
 
      while 在disk-has-space 磁盘为空的时候,添加文件,如果磁盘没空了,就退出循环.
  do-while 则是先加一个文件,再判断.
 
 
  三. 块:表达式组
      Blocks: groups of expressions
 
      在newLISP中很多函数都可以构造代码块:由一组表达式组合起来的列表.他们中大多
  数是隐式的.比如刚刚介绍的 while 和 until . 你可以看到他们的 <body> 都包含了不
  止 1 条语句.
      但是在某些时候还是需要显示创建代码块的,这时候我们就需要用到 begin , and ,
  or 三个函数了.
      begin 可以把多个表达式创建组合进一个列表,然后依序执行每个语句.
 
 
- (begin
  (switch-on)
  (engage-thrusters)
  (look-in-mirror)
  (press-accelerator-pedal)
  (release-brake)
  ...)
 
 
      当你需要执行多行语句,而你调用的函数又只接受一个表达式,这时候 begin 就能派
  上用场了.比如 if .
 
 
- (if (困了)
      (begin (刷牙) (睡觉))
      (工作)
  )
 
 
      begin 中的各个表达式会依序执行,互不影响.只有最后一个表达式的计算值会作为作
  为整个 begin 表达式的返回值,别的表达式的返回值都被丢弃了.而且其中一个表达式执
  行错误,也不会中断整个代码块的执行(当然前提不能太严重).
 
- (begin
      (println "so far, so good")
      (= 1 3) ; 尽管返回了nil 但是没问题 继续执行下一句
      (println "not sure about that last result"))
 
  ;输出
  so far, so good
  not sure about that last result!
 
 
  2: and 和 or
 
      and 和 begin 一样,也是用来构建代码块的.
 
  >(help and)
  syntax: (and <exp-1> [<exp-2> ... ])
 
      不过 and 会检查每一个表达式的返回值,只要碰到一个表达式的返回值不是 true ,
  就会推出 and 表达式,同时忽略剩下的表达式.
      下面整个代码块可以用来监测 disk-item 是否是一个有效的文件夹名:
 
- (and
      (directory? disk-item)
      (!= disk-item ".")
      (!= disk-item "..")
      ; 上面三个测试都成功才能到达这里
      (println "it looks like a directory")
 
 
      要通过三个测试, disk-item 必须是一个目录(directory? <str-path>),同时还不能
  是"." (当前目录),也不能是".." (上一个目录) ,只有这些都符合了,才会执行最后一个
  表达式,打印确认信息.
 
 
- (and
      (< c 256)
      (> c 32)
      (!= c 48))
 
 
      根据 c 值得不同,返回 true (大于 32 小于 256 同时 不等于 48)或者 nil .
      在很多时候可以,面对比较短的 if 语句,我们可以考虑用 and 替换.
 
 
- (if (number? x)
-     (begin
          (println x " is a number ")
          (inc 'x)))
 
      下面是 and 的版本:
 
- (and
      (number? x)
      (println x " is a number ")
      (inc 'x))
 
 
      也可以使用 when :
 
 
- (when (number? x)
      (println x " is a number ")
      (inc 'x))
 
      注意(inc 'x) 和 (inc x) 是不一样的,前者不会改变x,这可以算一个技巧.
      其实更常见的情况是,在某个函数的判断表达式部分使用 and :
 
 
- (if (and (< x 3) (> x -4))
      (println "-4 < " x " < 3"))
 
 
      or 函数和 and 函数比起来更宽容.
 
  >(help or )
  syntax: (or <exp-1> [<exp-2> ... ])
 
      他只需要有一个表达式计算为true 就返回,并且忽略剩下的表达式.
 
 
- (for (x -100 100)
- (or
      (< x 1) ; x 不能小于 1 否则 就返回了
      (> x 50) ; 也不能大于50
      (> (mod x 3) 0) ; 必须能够被 3 整除
      (> (mod x 2) 0) ; 必须能够被 2 整除
      (> (mod x 7) 0) ; 必须能够被 7 整除
      (println x)))
 
  42 ; 最后我们得到终极之数
 
 
 
  四. 混沌流: amb 函数
      ambiguity: the amb function
 
      amb函数会随机抽取一个列表执行,并返回.之前你无法知道他会抽取哪个表达式.
 
 
  > (amb 1 2 3 4 5 6)
  3
  > (amb 1 2 3 4 5 6)
  2
  > (amb 1 2 3 4 5 6)
  6
  > (amb 1 2 3 4 5 6)
  3
  > (amb 1 2 3 4 5 6)
  5
  > (amb 1 2 3 4 5 6)
  3
  >...
 
 
      我们也可以用他来进行随机操作:
 
 
- (dotimes (x 20)
- (amb
      (println "Will it be me?")
      (println "It could be me!")
      (println "Or it might be me...")))
 
 
  Will it be me?
  It could be me!
  It could be me!
  Will it be me?
  It could be me!
  Will it be me?
  Or it might be me...
  It could be me!
  Will it be me?
  Will it be me?
  It could be me!
  It could be me!
  Will it be me?
  Or it might be me...
  It could be me!
  ...
 
 
  五.  选择: if , cond , 和 case
       Selection: if, cond, and case
 
 
      当我们需要根据不同的判断结果,选择相应的操作时,就需要用到 if , cond 和 case
  .
 
 
  >>(help case)
  syntax: (case <exp-switch> (<exp-1> <body-1>) [(<exp-2> <body-2>) ... ])
 
 
      <exp-switch> 就是判断表达式, case会将其计算的结果与后面的 exp-1 exp-2 ...
  匹配.匹配成功的则执行相应<body-1> <body-2> ... ,执行完后返回.
      (<exp-1> <body-1>) 是value/expression 值/表达式 对,符合exp-1则执行body-1
  ,不符合则找寻下一个value/expression,直到找到匹配的value,执行相应expression,然
  后将其执行结果作为整个case 表达式的返回值返回.如果找不到匹配的valule,则返回nil
  .
 
- (case n
      (1 (println "un"))
      (2 (println "deux"))
      (3 (println "trois"))
      (4 (println "quatre"))
      (true (println "je ne sais quoi")))
 
 
      case会根据 n 值得不同打印不同的内容,如果前 4 个选项都不匹配就会打印最后一
  个字符串. 最后加一个 ture 条件非常有用,这样可以起到收尾的作用.不管 n 值是多少,
  都会符合 true 判断. 你可以用 nil 试验下.
      当然 (1 (println "un") (println "ok")) 这样也是可以的.
      有一点要注意的就是, (<exp-1> <body-1>)中的 <exp-1> 不会被计算:
 
 
- (case n
      ((- 2 1) (println "un"))
      ((+ 2 0) (println "deux"))
      ((- 6 3) (println "trois"))
      ((/ 16 4) (println "quatre"))
      (true (println "je ne sais quoi")))
 
 
      上面代码中的前 4 个选项都不会计算,他们只会作为普通的列表被匹配.如果 n 是 1
  你也许想(- 2 1) 能够计算出 1 , 然后匹配n, 接着打印 "un" ,但是case 不会这样做,
  这个选项只是'(- 2 1) , 一个列表, 除非你将 '(- 2 1) 赋值给 n . 才会打印 "un" .
      如果你想(- 2 1) 计算怎么办? 用 cond . 或者用宏自己改装 case .
 
 
- (cond
      (test action1 action2 ...)
      (test action1 action2 ...)
      (test action1 action2 ...)
      ...
  )
 
 
      在之前介绍 if 的时候,我们提到过 cond ,在nl里,他就是传统版本的 if .
      cond 后 就是一个个 test/action 列表.
      每一个 test/action 列表,都有开始的一个 test 判断表达式,加上其后的 action
  执行表达式组成. nl 依次检验每个test ,如果 test 计算的值为 true ,则执行其后的
  action序列.然后退出 cond .
      下面看一个典型的样例:
 
 
- (cond
      ((< x 0) (define a "impossible") )
      ((< x 10) (define a "small") )
      ((< x 20) (define a "medium") )
      ((>= x 20) (define a "large") )
  )
 
 
      根据 x 值的不同,定义不同的a值.
      test 部分不局限于表达式,也可以是一个值,一个symbol.
 
      如果你用 if 的会,会跟简洁.
 
 
- (if
      (< x 0) (define a "impossible")
      (< x 10) (define a "small")
      (< x 20) (define a "medium")
      (>= x 20) (define a "large")
  )
 
 
      很多人会说既然 if 那么方便,看的也舒服,为什么还留着 cond .
      我刚从cl装到nl上的时候,也是用 cond 居多,那时候就觉得 cond 很规范. 后来也渐
  渐会用到 if . 短小的代码可以使用 if ,复杂的代码用 cond  ,比如有多条 action语句
  的时候.
 
 
- (cond
-     ((< x 0) (define a -1) ;如果用 if 的话,就必须调用 begin 函数
               (println a)   ; 但是 cond 则不需要
               (define b -1))
      ((< x 10) (define a 5))
      ...
 
 
 
  六. 控制结构中的局部symbol
      Variables local to a control structure
 
      在 dolist , dotimes , 和 for 中都用到了局部symbol, 这些symbol 只在 循环语
  句中有效.出了循环语句就消失了.
      下面我们用 let 和 letn 显式的申明局部 symbol.
 
 
  > (help let)
  syntax: (let ((<sym1> [<exp-init1>]) [(<sym2> [<exp-init2>]) ... ]) <body>)
  syntax: (let (<sym1> <exp-init1> [<sym2> <exp-init2> ... ]) <body>)
 
 
      第一行的let 语法就是传统lisp中的语法,而第二行则是nl中更加简化的语法.
      两种语法都可以使用,就看你喜欢哪种了.
      下面我们用第二种语法讲解.
      let 后面紧接着的第一列表就是,用来申明 symbol 的 ,他们是一个个 sym/value对.
      <sym1> <exp-init1> 中的 <sym> 就是 symbol名,而 <exp-init>则是初始化值.这个
  值可以用表达式可以用symbol,可以用值,还是那句话 all is data, code is data.
      在初始化完成后,这些sym就可以在 <body> 中使用了,执行完成后,退出 let 语句,这
  些sym就消失了,在 let 语句外我们无法继续被引用 let 内的 sym.
      先看个简单的:
 
 
  >(let ((x 1) (y 2)) (+ x y))
  3
  ;(let (x 1 y 2) (+ x y))
  ;两种语法都是一样的
 
 
      这里使用了第一种语法,初始化x 为 1 , y 为 2 .然后执行(+ x y) ,计算结果作为
  let 语句的返回值返回.(在nl中,列表的返回值就是最后一条执行的语句).
      提示:对齐初始化部分可以大大提高代码可读性.我个人是很喜欢把代码写宽大点得.
  尽管我的显示器不是很大,但我认为把函数写的太复杂不是个好习惯.在代码紧凑和优雅上
  ,我会优先选择后者.
 
- (let
-     (x (* 2 2)
       y (* 3 3)
       z (* 4 4)
      )
      ; 初始化结束
  (println x)
  (println y)
  (println z)
  )
 
 
      这个例子看起来更复杂了点,不过他使用的是第二种语法.初始化了x y z 三个值,然
  后使用三个 println 语句将他们打印出来.
      如果你在初始化 y 的时候要用到 x 的值怎么办, 你可以用 let 试验下,正常情况下
  会提示一个 nil 错误,let 找不到 x的值,自动判定x 为 nil . newLISP里任何没有赋值
  过的symbol 系统再你调用他的时候,都会给你个 nil 值.
      这时候我们就要用到letn
      这个函数和传统lisp里的let*很像.
 
 
- (letn
-     (x 2
      y (pow x 3)
      z (pow x 4))
  (println x)
  (println y)
  (println z))
 
 
  ;输出结果
  2
  8
  16
 
 
      在定义 y 和 z 的时候,我们引用了 x 的值,如果是使用 let ,这里则会报错.不过记
  得,我们不能再定义 x 的时候 引用 y ,因为那时候 y还没定义.
 
 
  七.  定义自己的函数
      Make your own functions
 
 
      现在到了本节最激动人心的时刻,因为只要能够定义你自己的函数,你就可以改变所有
  的newLISP函数,完全打造你自己的语言,你可以模仿C,模仿JAVA,模仿CL,模仿......
 
      你也可以完全抛弃这些老家伙,创造一套属于自己的全新语言.
 
      从此你不再需要遵循他人的法则,在这里,在这个自由的世界,作为一个自由的人,你唯
  一要做的就是遵循你自己.
 
      Just Do You !!!
 
 
- (define (func1)
      (expression-1)
      (expression-2)
      ...
      (expression-n)
  )
 
 
      定义函数我们使用 define , func1是函数名, 这个函数没有参数.从(expression-1)
  到(expression-n) 就是函数需要执行的表达式.函数也可以有参数.
 
 
- (define (func2 v1 v2 v3)
      (expression-1)
      (expression-2)
      ...
      (expression-n)
  )
 
 
      v1 , v2 , v3就是函数func2的参数.正常情况下我们用下面的方法调用这2个函数.
 
 
  (func1) ; 无参数
  (func2 a b c) ; 3 个参数
 
 
      函数也是 list , 他最后一个执行的表达式 (expression-n) 的结果,会作为整个函
  数的返回值返回.
      下面这个函数根据 n 值得不同返回 true 或者 nil .
 
 
- (define (is-3? n)
      (= n 3))
 
  >(println (is-3? 2))
  nil
  >(println (is-3? 3))
  true
 
 
      之前说过表达式和元素是可以互换的.作为函数的返回值,也可以直接使用某个值,或
  者symbol.
 
 
- (define (answerphone)
              (pick-up-phone)
              (say-message)
              (set 'message (record-message))
              (put-down-phone)
              message)
 
 
      这里的返回值就是 symbol message.
      在函数的参数列表中定义的 symbol ,他们的作用范围只在函数内.其实这相当于隐式
  申明的局部变量,接下来马上会介绍.
 
 
- (define (test v1 v2)
      (println "v1 is " v1)
      (println "v2 is " v2)
      (println "end of function"))
  >(test 1 2)
  v1 is 1
  v2 is 2
  end of function
 
  >v1
  nil
  ;函数外v1 无效
 
 
      但是在函数内手工重新创建一个symbol,这个symbol则会影响到函数外,在某个程度上
  这很不安全.不过可以通过创建局部变量来解决(之前的let letn 就可以创建 局部变量).
 
  ;(define (test v1 , x) ;真正编码的时候会这样写
- (define (test v1)
      (set 'x v1)
      (println x))
 
  >(test 1)
  1
  >x
  1
 
  ;在函数外x 依旧有效
 
 
      如果你提供给函数的参数多了,少了会怎么样呢?
 
- (define (test)
      (println "hi there"))
 
 
  >(test 1 2 3 4) ; 1 2 3 4 全部被忽略 不过你可以通过 $args 引用他们
  hi there
 
 
      newLISP 再一次体现了她得灵活性,她可不会向你碎碎念.=_~
 
 
- (define (test n)
      (println n))
 
  >(test) ; 没有提供值给n , nl 自动给n赋值nil
  nil
 
  >(test 1)
  1
  >(test 1 2 3) ; 2 和 3  被忽略
  1
 
 
  八. 局部变量
      Local variables
 
 
      局部变量让一切泾渭分明.
      当你不想外部的symbol影响到函数内的同名symbol的时候,或者不想函数内的操作,影
  响到函数外部的同名symbol的时候.你就需要申明局部变量了.
 
- (define (changes-symbol)
      (set 'x 15)
      (println "x is " x))
 
  >(set 'x 10)
  10 ;-> 函数外我们 申明 的 x 等于 10
 
  >(changes-symbol)
  x is 15  ;在函数内改变x
 
  >x
  15  ;函数外的x 被同时被改变了这不是我们想要的
 
 
      我们可以用之前的let 和 letn :
 
 
- (define (does-not-change-x)
-     (let (x 15)
      (println "my x is " x)))
 
  >(set 'x 10)
  10
  >(does-not-change-x)
  my x is 15
  >x
  10  ;函数外的x 没被改变
 
 
      我们再函数内申明了一个 x 并将 15 赋值给他, 这个x是和函数外的x相互独立的.最
  后我们看到外部的x依旧是10.那如果我们再函数内设置x内?
 
 
- (define (does-not-change-x)
-     (let (x 15) ; 这个 x 是 let 申明的
      (set 'x 20)))
 
  >(set 'x 10) ; 这个 x 是函数外部 申明的
  10
 
  >x
  10 ;-> 外部的 x 值为10
 
  >(does-not-change-x)
  20 ;最后一个表达式 (set 'x 20) 的计算结果,作为返回值.
 
  >x
  10 ;-> 外部的x 依旧不受任何影响
 
 
      除了 let 和 letn , local 也可以用来申明局部变量,不过你不能给这些局部变量赋
  值,他们只能初始化为 nil .
 
 
- (define (test)
-     (local (a b c)
              (println a " " b " " c)
              (set 'a 1 'b 2 'c 3)
              (println a " " b " " c)))
 
  >(test)
  nil nil nil ;我们看到 a b c都被初始化成了 nil
  1 2 3
 
 
      最后来看一种更"淫荡"的写法:
 
 
- (define (my-function x y , a b c)
      ; a b  c 被 nl 自动的申明成了局部变量
  ...)
 
 
      也许你会奇怪,这个 "," 号是关键字吗?
      答案是否定的, 在nl里头逗号仅仅是一个symbol,和其他千千万万的symbol一样,没有
  任何的特殊. 在这里逗号只是作为一个提示标记,人文的规定了后面的变量是局部变量,而
  不是函数参数.不过 define 还是把 , a b c 4个symbol 当做参数对待,一般我们只会给
  这个函数提供 2 个参数 , 这样 后面的 4个参数因为没有赋值,就都是 nil 了,这样刚好
  起到了申明局部变量的作用.只是这样申明的更方便快速.
 
 
  (set ', "this is a string") ; 逗号仅仅是个symbol
 
  >(println ,)
  "this is a string"
 
 
  1:  默认值
      Default values
 
 
      newLISP 支持定义函数的时候,提供参数默认值.
 
- (define (foo a  b c 2)
      (println a " " b " " c))
 
 
      可以这样提供默认值:
 
 
- (define (foo (a 1) b (c 2))
      (println a " " b " " c))
 
  >(foo) ; b 没有提供默认值所以是 nil
  1 nil 2
 
  >(foo 2) ; 给foo传递了一个数据,这个数据被赋值给a
  2 nil 2
 
  >(foo 2 3) ; a b 都接受到了新的值
  2 3 2
 
  >(foo 3 2 1) ; 默认值全部被覆盖了
  3 2 1
 
 
  2:  所有的参数:args
      Arguments: args
 
      newLISP 提供了一个函数给我们处理参数提供了极大的自由: args .
      args 返回所有的,提供给函数的,但是超出参数个数的数据.
 
- (define (test v1)
  (println "the arguments were " v1 " and " (args)))
 
  >(test)
  the arguments were nil and ()
 
  >(test 1)
  the arguments were 1 and ()
 
  >(test 1 2 3)
  the arguments were 1 and (2 3)
  ; 函数只申明了一个参数,所有多余的数据都传递给了 args
 
  >(test 1 2 3 4 5)
  the arguments were 1 and (2 3 4 5)
 
 
      通过args ,我们可以编写接受任何类似数据的函数.
- (define (flexible)
      (println " arguments are " (args))
-     (dolist (a (args))
      (println " -> argument " $idx " is " a)))
 
  >(flexible)
  arguments are ()
 
  >(flexible "OK")
  arguments are ("OK")
  -> argument 0 is OK
 
  >(flexible 1 2 3)
  arguments are (1 2 3)
  -> argument 0 is 1
  -> argument 1 is 2
  -> argument 2 is 3
 
  >(flexible '(flexible 1 2 "buckle my shoe"))
  arguments are ((flexible 1 2 "buckle my shoe"))
  -> argument 0 is (flexible 1 2 "buckle my shoe")
 
 
      args 运行你传递任意数量的数据给函数.
 
 
- (sum 0 1 2 3 4 5 6 7 8 ...
  999997 999998 999999 1000000)
  ; 在这里忽略了n个数 各位同学可用用(sequence 1 100000) 生成
  ; 结果如下
  500000500000
 
 
      newLISP 提供了doargs 函数,用来专门的遍历 args .
 
 
- (define (flexible)
      (println " arguments are " (args))
-     (doargs (a) ; 替代dolist
          (println " -> argument " $idx " is " a)))
 
 
          nl 里头还有很多的流控制函数,大家可以多看看手册,比如 catch throw .比如
  silent , 无声版的 begin  .
 
 
  3.  作用域
      Scope
 
      让我们思考下面这个函数:
 
 
- (define (show)
      (println "x is " x))
 
 
      在函数内部引用了一个不确定的symbol x.
      后果非常的严重,函数非常的生气.
      函数在执行的时候就会自动的去附近寻找 x ,由近及远 .
 
 
- (define (show)
      (println "x is " x))
 
  >(show)
  x is nil
 
  >(set 'x "a string")
  >(show)
  x is a string
  >
- (for (x 1 5)
-     (dolist (x '(sin cos tan))
      (show))
      (show))
 
 
 
  x is sin
  x is cos
  x is tan
  x is 1
  x is sin
  x is cos
  x is tan
  x is 2
  x is sin
  x is cos
  x is tan
  x is 3
  x is sin
  x is cos
  x is tan
  x is 4
  x is sin
  x is cos
  x is tan
  x is 5
 
  >(show)
  x is a string
 
  >
- (define (func x)
      (show))
      (func 3)
 
  x is 3
 
  >(func "hi there")
  x is hi there
 
  >(show)
  x is a string
 
 
      你们仔细看,就会发现,函数是一层层找上去的.这叫做动态作用域.
      在某个程度上说,这很符合现实中的环境规律.
 
      但是我们再编程的时候最好别这样做,上面只是演示.因为引用不确定的symbol,容易
  使得代码混乱,也容易出bug.
 
      最好的习惯就是:
      1 . 多编写解释性的symbol, 不能到处 x y z ...
      2 . 多使用局部变量 , 可以把全局的先赋值给局部,明确的说明 symbol 来自于哪里
      3 . 就算要直接引用全局变量,也最好写个注释,全局变量用大写.
 
      以后我们会介绍 context ,使用 context 可以更大程度上的提高代码结构化,使得代
  码更清晰,更优美.
 
 
 
 
      依照这趋势,估计都快把 introduction给翻译过来了 . 工程浩大, 光这节就写了2天
      吃饭去,同学们!
 
  2012-04-04 22:16:38
 
  html 彩色版本请看 http://code.google.com/p/newlisp-you-can-do
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

转载于:https://my.oschina.net/darkcode/blog/60382

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值