精华内容
下载资源
问答
  • 关于scheme缩写

    千次阅读 2015-08-05 11:10:28
    REPL Read Eval Print Loop ...RNRS Revised^n Report on the Algorithmic Language Scheme R6RS Revised6 Report on Schemehttp://www.scheme.com/csug8/ http://www.scheme.com/tspl4/ http://www.scheme.com/tspl
    展开全文
  • 常用的URL Scheme客户端配置

    万次阅读 2015-11-16 10:29:18
    常用的URL Scheme缩写: http://www.coneboy.com/?p=1305 iOS 客户端URL Scheme配置以及使用 由于iOS采用沙盒机制,所以,app之间的跳转可以使用URL Scheme来实现,还好注册比较方便,同时URL Scheme也能...

    常用的URL Scheme缩写:   http://www.coneboy.com/?p=1305


    iOS 客户端URL Scheme配置以及使用

    由于iOS采用沙盒机制,所以,app之间的跳转可以使用URL Scheme来实现,还好注册比较方便,同时URL Scheme也能传递参数。

    1.URL Scheme的配置

    info.plist中按下图配置

    2.URL Scheme使用

    1
    [ [UIApplication sharedApplication ] openURL : [ NSURL URLWithString : @ "kkapp://" ] ];

    3.查找其他APP的URL Schemes

    上这个网站 URL Schemes 查一下相应的 app 的 URL Scheme 是否有被收录
    第一种方法没找到的话,把相应的 app 的 ipa 安装文件下载下来,把文件 .ipa 的后缀改成 .zip,然后解压,打开 Payload/xxx.app/Info.plist 这个文件,找到 URL types 下的 URL Schemes 下的数组对应的值就是这个 app 的 URL Scheme 了,以 Weico 为例:

    4.验证是否有效

    在真机设备(此设备要安装了待验证的 app)里面打开 Safari,然后在地址栏中键入该应用的 URL Scheme,后加 ://,比如 kkapp 的,在地址栏中键入 kkapp:// ,然后点击确定,如果能正常调用出 Weico,即代表这个 URL Scheme 正确可用

    5.目前一些常用的

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    //launch center pro支持的参数主要有两个,[prompt]文本输入框和[clipboard]剪贴板

    //淘宝宝贝搜索 taobao://http://s.taobao.com/?q=[prompt]
    //淘宝店铺搜索 taobao://http://shopsearch.taobao.com/browse/shop_search.htm?q=[prompt]

    omnifocus : ///add?name=[prompt]&note=[clipboard]
    mdict : //[prompt]

    QQ的url是 mqq : //
    微信是weixin : //
    淘宝taobao : //
    点评dianping : // dianping://search
    微博 sinaweibo : //
    名片全能王camcard : //
    weico微博weico : //
    支付宝alipay : //
    豆瓣fm:doubanradio : //
    微盘 sinavdisk : //
    网易公开课ntesopen : //
    美团 imeituan : //
    京冬openapp.jdmoble : //
    人人renren : //
    我查查 wcc : //
    1号店wccbyihaodian : //
    有道词典yddictproapp : //
    知乎zhihu : //
    优酷 youku : //

    6.更多的URL Schemes(wiki)
    http://wiki.akosma.com/IPhone_URL_Schemes

    7.使用

    1
    2
    3
    4
    5
    6
    7
        NSURL * myURL_APP_A = [ NSURL URLWithString : @ "kkapp://" ];
        if ( [ [UIApplication sharedApplication ] canOpenURL :myURL_APP_A ] ) {
            NSLog ( @ "canOpenURL" );
            [ [UIApplication sharedApplication ] openURL :myURL_APP_A ];
        } else {
            NSLog ( @ "不能打开" );
        }

    ================


    感谢:
    http://blog.csdn.net/ba_jie/article/details/6884818
    http://www.cocoachina.com/newbie/tutorial/2012/0529/4302.html
    http://www.zhihu.com/question/19907735
    ================

    本文版权归属coneboy.com
    欢迎转载,并请注明出处。


    展开全文
  • scheme 学习

    千次阅读 2016-05-06 17:19:24
    Scheme共舞

    与Scheme共舞

    发表在《程序员》2007年7月刊上。不log上写帖子不用考虑版面限制,所以这里的帖子比发表的啰嗦点。赵健平编辑,Jacky,和刘未鹏都给了我很多帮助,在这里一并谢了。免费的Scheme实现很多。我用的是PLT Scheme,可以到这里下载。PLT Scheme的IDE(Dr. Scheme)支持Emacs的键盘绑定,用emacs的老大们应该喜欢。Dr.Scheme内置中文支持:

    下面是正文:
    不能影响你思考方式的编程语言不值得学习 – Alan Perlis [1]
     
    不少朋友问,为什么要学Scheme这样无数括号包裹的语言。答案很简单:帮你理解计算的本质,成为更优秀的程序员。Scheme好比大还丹。没人拿药丸儿当板砖拍人,但服了它却能指望十步杀一人,千里不留行。
     
    1975年问世的Scheme是Lisp方言。所以不妨从Lisp谈起。Lisp是一门传奇语言,诞生50年,仍然影响深远。程序员们似乎不断“发现”Lisp里简单却深刻,浅显但强大的特性,并应用到不同地方,取得非凡成就。比如最近热火的Ruby、Python、以及JavaScript中许多为人称道的功能源于Lisp。也许John K. Foderaro的比喻和总结最能说明Lisp的价值:Lisp好比变色龙,高度适应环境的改变,因为 它是一门可以编程的编程语言。我们不仅可以用Lisp编程,还可以对Lisp编程 [2]。Lisp内置的抽象方法让Lisp程序员们身段灵活,长袖善舞。每当新的编程范式出现,Lisp程序员们总能快速实现相关功能,甚至做出进一步改进。比如Smalltalk展示面向对象编程的潜力后,MIT媒体实验室的Cannon Howard便在1982年推出Flavors,一个功能丰富的面向对象扩展。Cannon的扩展不仅实现了 当时流行的面向对象功能,还首创了多继承,多重分派,以及被Ruby程序员狂赞的mixin [3]。尔后在Xerox PARC的 Gregor Kiczales又在集大成的Common Lisp面向对象扩展 — CLOS — 里加入面向方面(AOP)的编程方法 [4]。Gregor也是面向方面编程的发起人和AspectJ的作者。熟悉Java的老大应该对他不陌生。其实CLOS支持的method combination已经支持AOP里的before/after/around处理。AOP和CLOS出于同一人之手,应该不是巧合。顺便说一句,Gregor1993年的名作 The Art of Meta Object Protocol也值得细读。
     
    传奇语言自有传奇历史。1958年,John McCarthy从达特茅斯搬到MIT。当时人工智能的另一奠基人Marvin Minsky也在那里。牛人相见,好比利刃相击,火花耀眼。著名的MIT人工智能计划上马 [5]。研究AI的过程中,McCarthy需要一门编程语言描述他的理论模型。当时人见人爱的图灵机只有一套笨拙的语言,不适合干净利落地表达复杂的递归函数,所以McCarthy在丘齐的lambda算子基础上设计了Lisp。早期的Lisp是纯理论工具,用来帮助项目组进行程序的推导和证明。实在需要用机器验证理论了,研究组的老大们就手工把Lisp程序翻译成IBM 740的汇编代码,再上载到IBM 740上运行。人肉编译器们甚至热衷于编译各式Lisp程序,觉得跟解智力题一样好玩儿。他们还证明了可以用Lisp写出一个通用函数eval(), 用来解释执行Lisp的表达式 [6]。但他们光顾赞叹eval()和元图灵机一样彪悍,且比图灵机构造出元图灵机的代码美妙,并没想到eval就是一个通用的Lisp解释器。幸好有天McCarthy的学生S.R. Russell灵机闪现,连夜用IBM704的机器语言实现eval()。于是世界上第一个Lisp解释器横空出世,绿色低功耗无污染的人肉编译才渐渐失传。那时真是计算机科学研究的黄金时代啊,人们可以一夜之间改变世界,比居委会大妈在股市一夜暴富还来得轻快。顺便提一下,我们习以为常的条件判断语句,也是McCarthy在Lisp里发明的。而为了让函数应用没有副作用和实现函数闭包,McCarthy的研究小组又顺便发明了垃圾收集。1975年,同是MIT的Gerald Jay Sussman和Guy Steele为了研究Carl Hewitt的面向对象模型,用Lisp编写了一个玩具语言。这个玩具语言简化了当时流行的Lisp语法,引入了词法定界(又叫静态范围)和Continuation两大革新。Sussman和Steele给这门语言取名Schemer,希望它发展成像AI里著名系统Planner一样的有力工具。可惜当时MIT用的操作系统ITS只允许最长6个字节的文件名。Sussman和Steele不得不把Schemer的最后一个字幕’r’去掉。Scheme问世便显露峥嵘:Sussman和Steele很快发现Scheme的函数和Hewitt模型里的演员(也就是我们现在所谓的对象)没有本质区别,连句法都基本一致 [7]。事实上,Sussman在教材《计算机程序设计与解释》的第二章用短短几十行代码展示了一套面向对象系统。
     
    Scheme是极度简化的语言。他的规范文档不过47页 [8]。相比Lisp另一大分支Common Lisp规范的上千页文档或者Java规范的500来页文档,可见Scheme的短小精悍。不过,我们仍然可用Scheme写出优雅犀利的程序。Scheme规范R 5RS开篇道出了Scheme的设计宗旨:设计编程语言时不应堆砌功能,而应去掉让多余功能显得必要的弱点和限制。Smalltalk的发明人Alan Kay在一次访谈录中提到,Lisp是编程语言中的麦克斯韦方程组 [9]。这句评价用到Scheme上更为合适。Scheme甚至让我们写出用其他语言无法轻易写出的程序。Sussman和Steele用Scheme探索不同的编程模型时时,往往一周做出十来种不同的解释器,可以旁证Scheme的简洁和灵活。在解释是什么造就了Scheme的精练与生猛之前,我们先介绍一下Scheme的基本元素:
     
    • Scheme的语法结构 大道至简。Scheme的结构就两种:原子和表达式。原子是诸如数,字符串,布尔值,变量,空表这类简单数据。对非变量的原子求值,得到原子自身。对变量求值,得到变量绑定的值。比如说,对1求值得到1,但如果对变量A求值,而A和字符串”A”绑定,则得到字符串“A”。表达式的形式也只有一种:列表。一对括号包含起来的就是列表。表里的元素用空格分开。列表可以嵌套。这样的表达式在Lisp里叫做S-表达式,意思是符号表达式。下面是一些例子:
      • ( ): 一个空表
      • (1 2 3 4 5):一个包含五个整数的表
      • (1 “a” 1.5 #t #f):一个列表,依次包含整数、字符串、浮点数、为真的布尔值、和为假的布尔值
      • (1 (2 3) ):一个嵌套列表,第二个元素(2 3)也是一个表
      • (+ 2 3):一个表达式,表示把2和3相加。Scheme里所有的操作符都是前缀操作符,即操作符在前,操作数据在后。比如说4 * (2 + 3)在Scheme里表达为(* 4  (+ 2 3))。很多人看不管这种方式。不过仔细思考一下,可以看出前置操作符让任何操作符都是多维的。比如说。如果我们要把1到5的整数相加,用中缀操作符,就得写成 1 + 2 + 3 + 4 + 5。同一个加好重复了4次。而用前缀操作符,只需要写一次:(+ 1 2 3 4 5)。推而广之,如果我们要把一列数加起来,就得用到循环。而在Scheme里则不需要。而且前缀操作符去掉了优先级问题:我们可以通过括号来判断每个表达式的优先级。
      • (lambda (x y) (sqrt (* x y))。这个表达式定义了一个匿名函数,计算并返回参数x和y的几何平均值。当一个表达式以lambda开头的时后,我们就知道要定义一个函数了。
      • (define zero 0):这个表达式把一个变量zero绑定到一个整数0。在Scheme里,所有变量本质上都是指针。指针本身没有类型,他们指向的值才有类型。换句话说,Scheme是动态类型语言。
      • (car  ‘(1 2 3 4)):这个表达式调用函数car。函数car接收一个列表参数,并返回这个参数的第一个值,也就是1。注意例子里的参数(1 2 3 4)前有一单引号。这是因为Scheme总是把一个普通列表当作表达式计算。加上单引号相当于告诉Scheme,不要对(1 2 3 4)估值,把它当成数据对待。如果不加这个单引号,Scheme会执行(1 2 3 4)。执行的规则是把该列表的第一个元素当成函数来调用。而第一个元素是1,不是函数,Scheme会抛出错误。
      • (cdr ‘(1 2 3 4)): 这个表达式调用函数cdr(读作kuder)。函数cdr也是把一个列表作为参数,并返回这个列表除去第一个元素后的子表。所以对(cdr ‘(1 2 3 4))求值,就得到(2 3 4)。
    • Scheme的数据类型 Scheme提供了各种通用的数据类型:整数,浮点数,复数,有理数,字符串,布尔变量,散列,数组,矢量,点对,和列表。值得一提的是点对(pair)和列表。这俩哥们儿是Scheme编程的基石。还是用例子说明比较好:
      • (1 . 2)是一个点对。一个点对包含两个指针,每个指针指向一个值。我们用函数cons构造点对。比如说(cons 1 2)就构造出点对(1 . 2)。因为点对总是又函数cons构造,点对又叫做cons cell。点对左边的值可以用函数car取出来,右边的值可以由函数cdr取出来。下面是图示:  
      • 如果一个点对右边不是一个值,而是一个指针,指向另外一个列表,我们就得到了列表。比如下面的图表示列表(1 2 3 4),实际上由点对构成:(1 . (2 . (3 . 4. ‘())。可以看出,列表本质是单向链表。 
     
      • 不要小看了列表。这个看似简单的数据类型的具有丰富的表达能力。比如我们可以把下面2x3的矩阵表达为((1 2 3) (4 5 6) (7 8 9)):  而下面的树也可以用列表直观表达:(A B C (D (F H) G) E)。也就是说,每个列表表示一个树或子树。列表的第一个元素是根。
    • 函数 函数在Scheme里是一等公民。定义的函数可以被当成数据传递或返回。有三种定义函数的方法:
      • lambda操作符定义一个匿名函数。比如(lambda (x) (* 2 x))定义了一个函数,返回参数x的倍数。操作符lambda后第一个子列表是参数列表,而第二个子列表是函数定义。这和JavaScript里的匿名函数没有本质区别: function(x){return 2 * x;}
      • 用define绑定函数名:(define 1+ (lambda (x) (+ 1 x)))。这个例子定义了递加函数,并把它绑定到函数名1+上。。Scheme对函数名没有限制。事实上,Scheme对所有函数名一视同仁。规范里定义的函数没有特殊地位,我们完全可以用自己的函数定义取代。这相当于下面的JavaScript语法: var increment = function(x){return x + 1;}。
      • Scheme还提供了一条捷径,省去lambda。下面的例子用大小比较定义相等函数。函数名是same? 而参数就是后面的x和y。         (define (same? x y)             (not (or (> x y) (< x y))) 这样的定义方式和JavaScript里的常用函数定义方式一致。呵呵,可以看出JavaScript从哪里获得灵感的了吧?下面是等价的JavaScript定义: function isSame(x, y){          return !((x > y) || (x < y)); }

    很多老大看不惯括号。其实Lisp刚诞生时,John McCarthy设计了叫M-表达式的语法,与C/C++的语法类似。比如S-表达式(cdr ‘(1 2 3)用M-表达式就写成cdr[1, 2, 3]。但是Lisp的程序员们纷纷放弃了M-表达式,选择直接使用S-表达式。S-表达式的实质是用抽象句法树(AST)表达程序,直接省去了解析这道工序。比如说,a+b*c解析成AST后,和下图一致。而该AST的表示不正好是(+ a (* b c))么?  更重要的是,既然程序就是句法树,程序和数据的表示就统一了。程序即数据,数据即程序。我们遍历列表修改数据。同理,我们也可以遍列类表修改程序。正是这样的统一处理带给Scheme无与伦比的威力:无论是编译时还是运行时,我们都可以修改,注入,加载,或者生成新的程序 — 这些无非是在AST里修改或添加节点而已。我们甚至可以改动或添加新的句法。 

    明了这些基本概念,就可以领略Scheme的妙处了。Scheme最为人称道的功能之一是它的函数编程能力。所谓函数编程,是指用一系列函数应用实现程序。每个函数接受参数,计算后返回结果。计算过程中没有副作用,不改变任何变量的状态。同时,函数本身是一等公民,可以作为数据传入另外的函数,也可以作为结果被其它函数返回。这样的好处是什么嗫?一言以蔽之:黏合 [10]。我们用简单的函数描述系统的不同功能。每个函数高内聚,低耦合(参数进,结果出。没有副作用。想低内聚高耦合都不容易)。Scheme提供许多方便的工具把这些函数黏合起来。这种高度支持模块化编程的能力绝对让人惊叹。多说无益。看例子。
     
    §       定义一个函数sum-of-squares计算一列数的平方和。比如说(sum-of-squares ‘(1 2 3 4))返回的结果是30。下面是Scheme的代码。   测试结果:   如果哪位老大不觉得这个函数定义优雅的话,不妨试试用命令编程的方式重写。比如说用C,用Java,或者用Pascal。 解剖一下上面的函数定义:
    o        第一行 (define (sum-of-squares numbers) 表示定义一个函数。函数名为sum-of-squares,而函数接受一个参数。
    o        第二行是函数的定义。计算顺序是:先调用函数map,在把函数+(Scheme里一切都是函数。相加也是函数)应用到得到的结果上。
    o        函数map是一个高端函数。所谓高端,是指这个函数可以接受或返回函数。函数map接受两个或多个参数。第一个参数必须是函数,而其它参数则必须是列表。函数map会同步遍历所有的列表,并把第一个参数应用到遍历时遇上的每个元素,并把结果放到一个新表里。在上面的例子里,函数map的第一个参数是个匿名函数:(lambda (x) (* x x))。这个匿名函数接受一个参数,x,并返回x的平方。我们来看看(map (lambda (x) (* x x)) ‘(1 2 3))这个例子:map从遍历列表(1 2 3)开始,一次取出1, 2, 最后3。对每个取出的元素,应用第一个参数定义的函数。比如取出2时,应用(lambda (x) (* x x))就得到(* 2 2),结果为4。所以最后的结果就是(1 4 9)。
    o        顾名思义,函数apply负责应用函数。它接受两个参数。第一个参数是函数,第二个参数必须是列表。列表对应被应用函数接受的参数列表。比如说,(apply + ‘(1 2 3 4))就是把相加应用到参数(1 2 3 4)上,和(+ 1 2 3 4)等价。这里也显出了用前缀操作符的好处:每个函数都可以接受任意多个参数。再举个例子:(apply car ‘((a b c d)))等价与(car ‘(a b c d)),得到的结果是a。注意哈,函数apply的最后一个参数在传入第一个参数代表的函数时,最外面的一层括号被剥去。所以我们要把列表(a b c d)传给函数car,就得写成((a b c d))。
    我们在编程里往往需要处理一系列数据,比如说把对一列整数求和,找出一个文件中每行里的电话号码,把一列数据转换成另外一列数据。。。如果在普通的命令式语言里,我们会用各式循环来处理。问题是,其实这些循环极其相似:遍历列表中每个数据,对每个数据做出一定的处理。遍历本身都是一样的,不同的只是处理数据的方式。而Scheme正是通过函数map抽象出了遍历的普遍形式。处理数据的具体例子被抽象成了函数。最后通过高端函数这个“黏合剂”,让我们享受到如此妖娆的代码。熟悉 Google MapReduceApache Hadoop,或者 Ruby Starfish的老大们又猜对了:MapReduce的灵感来自函数编程里常用的map和reduce函数。MapReduce本身是用C++写的。这多少可以说明,哪怕我们只用主流编程语言,学习其它编程范式也能增长我们的功力。
     
    §       再来一个例子:求出两个矢量的点乘。比如说[a, b, c, d] x [e, f, g, h]就等于a*e + b*f + c*g + d*h。如果我们定义函数dot-product, 那么(dot-product ‘(1 2 3 4) ‘(5 6 7 8))就等于1x5+2x6+3x7+4x8 = 70:   这次函数map同步遍历两个列表,所以定义的匿名函数也接受两个参数。Scheme里的map函数可以同时遍历任意多个列表。Scheme里的函数调用都是S-表达式列表。所以遍历一个列表也好,多个列表也好,都是处理一个S-表达式的尾巴,没有本质区别。哪位老大有兴趣,不妨了解了Scheme宏的用法(后面会讨论)后,实现自己的map函数。
    §       还不够神奇?那写个矩阵转置函数怎么样? 所谓矩阵转置,是说把M x N的矩阵的行和列兑换,得到NxM的矩阵。比如下面的例子。给出矩阵A, A的转置矩阵A T就等于矩阵B:      如果我们定义了函数transpose,那么用上面的例子,调用函数(transpose ‘((1 2 3) (4 5 6) (7 8 9) (10 11 12))就应该得到((1 4 7 10) (2 5 8 11) (3 6 9 12))。实现这个函数得多少代码呢?请看—   一行代码,四个函数。还有比这更干净利落的么?我们具体分析一下:
    o        和前面描述的一样,函数应用从里到外进行。所以调用(transpose matrix)时,(cons list matrix)先被执行,然后函数map被应用到执行的结果上。
    o        list是Scheme提供的一个函数。它接受任意参数,并把所有参数一次放到一个列表里,然后返回这个列表。比如说(list ‘a)返回(a),(list 1 2 3 4)返回(1 2 3 4)。注意这里我们不用写成(list ‘1 ‘2 ‘3 ‘4),为Scheme里,对数字计算得到数字本身。最后一个例子:(list ‘(1 2) ‘(3 4) ‘(5 6))得到((1 2) (3 4) (5 6))。
    o        函数cons前面提到过。它接受两个参数,返回这两个参数合成的点对。比如说(cons ‘a ‘b)就得到(a . b),而(cons 1 ‘(2 3))就得到(1 2 3)。
    o        (cons list matrix)的目的是把函数list“注入”到表达式矩阵的表里。比如说,(cons list ‘((1 2 3) (4 56))就得到(list (1 2 3) (4 5 6)) 。这是什么?对了,我们轻而易举地在运行时生成了代码!
    o        最后的函数应用(apply map 。。。)就清楚了。用例子最好说明:如果我们的矩阵matrix等于((1 2 3) (4 5 6)),那(cons list matrix)得到列表(list (1 2 3) (4 5 6))。自然地,(apply map (cons list matrix))等于(apply map ‘(list (1 2 3) (4 5 6)),也就等于(map list (1 2 3) (4 5 6))。计算这个表达式,当当!我们得到最后结果((1 4) (2 5) (3 6))。转置完成。

    §      在处理树状数据时,我们往往需要知道树的最大深度(最大深度也叫树的高度)。一个节点的深度等于该节点到根节点间的路径数。下图中的树最大深度为3, 路径是A->D->F->H。现在我们写一个函数来计算一棵树的深度。 

    o        先得知道树的表式方式。我们就用前面提到的表示法:(A B C (D (F H) G) E)。
    o        应用高端函数和递归,我们的函数定义非常简单: 
    o        解释下出现的新函数:
    o        关键字cond是条件函数,相当于C语言里的switch…case。它的语法如下: (cond      ((条件1) (表达式1))      ((条件2) (表达式 2))      。。。 ((条件 n) (表达式 n)) (else (表达式 n+1))) 也就是说,当(条件 k)的计算结果为真时,(表达式 k)会被执行。执行完后,函数cond结束。最后的符号else是特殊元素,它的计算结果总是为真,这保证了当其它条件语句不为真时,else对应的表达式肯定会被执行。
    o        函数list?判断它的参数是否是列表。如果是,它返回真值,#t。不然返回假值#f。比如说,(list? ‘()) 返回 #t, (list? ‘(1 2))也返回#t,而(list? 1)返回#f。
    o        这个函数怎么执行,就留给老大们当练习题吧。
    如果Scheme里仅有高端函数,到现在也就不足为奇。很多语言都已支持函数编程。Python, Perl,Ruby,C#3.0都内建了各式函数编程的功能,更不必说其它的函数编程语言,比如Erlang, Haskell, OCaml等。甚至C++里都用模板搭出了一整套函数编程的类库(比如boost.lambda)。不过Scheme还有一套至今无可比拟的独门暗器:宏。说到宏,用C的老大们就笑了。用C++的老大们也笑了。好在此宏非彼宏。Scheme的宏和模板直接操作列表,根本就是Scheme语言的一部分,可以结合环境生成灵活的代码,甚至扩展Scheme故有的语法。
     
    我们先用一个网上随处可见的例子说明C里宏的局限。假设我们需要写一个通用的求平方函数:x*x: y*y直观的写法应该是                           #define SQAURE(x) x * x
    如果真这样写,就错了。如果我们计算 1/SQUARE(2),宏展开为1/2*2。结果我们得到1,而不是正确的1/4。于是我们改写一下总可以了吧:                          #define SQUARE(x) (x*x)
    还是不行!看这个例子:SQUARE(1+1),展开后变成(1+1*1+1),结果得到错误的3。于是我们把宏改写成                          #define SQUARE(x) ((x)*(x))
    但这样还是不行。SQUARE(x++)会被展开成((x++)*(x++)),x被错误地多递增了一次。所以我们再改:                          int temp;                          #define SQUARE(x) ({temp = x; temp * temp}) 可是这样的话这个宏只能接受整数,还引入一个全局变量,那我们还不如写成int square(x){return x * x;}。于是再改:                          #define SAUARE(x)  /
                                        ({typedef xtype =XTYPE x; xtype temp = XTYPE x; temp*temp; }) 这下可以了,但我们以后不能直接申明int x了,得用XTYPE这个typedef定义的类型。一个如此简单的宏都要耗费这么多考量,那再复杂一点的呢?C++的模板好一些,不过看看Boost的实现,就知道C++模板最好留给类库程序员 [11]。幸好,Scheme的宏提供了完全不同的体验。它让我们把编程中重复出现的模式抽象出来。这类抽象往往和具体应用有关,不适合在短小篇幅内举例。因此,我们用模拟其它语言的功能来举例。但不要误解Scheme的宏只适合写编译器或者DSL。
     
    先举个简单例子供老大们开牙。Perl和Ruby里有一方便的语言后置修饰,即把条件判断放到执行语句的后面。比如:             print “x > y” unless y >= x
    这相当于下面的语句:             if(! (y >= x) ){                  print “x > y”             }
    Scheme里没有unless这个关键词,也不能后置修饰条件。不过用上宏就不一样了:   测试结果: 
    寥寥两三行,我们不仅有了unless这种用法,还把它做成了后置修饰。宏是这样定义的:
    §       我们用函数define-syntax定义宏,du是这个宏的名字(do-unless的缩写)。缺省情况下,宏扩展从表达式的第一个元素开始,所以我加上du作为关键字。我们可以通过修改扩展宏的函数来去掉对起首关键字的依赖,不过这无关本质。
    §       每个宏由一系列句法规则组成。这些句法规则由syntax-rules定义。函数syntax-rules规定了一到多组模式匹配的语句:(模式 模板): (syntax-rules ()     (模式1   模板1)     (模式2   模板2)      。。。     (模式n   模板n)) Scheme会依次用列出的模板匹配定义的表达式。匹配成功的话,就扩展模板。比如说当Scheme看到(du (display “3 > 2”) unless (> 2 3))时,就开始试着用宏定义里的模式来匹配该表达式。下划线”_”是一特殊字符,指代宏的名字。匹配的结果是 _ 与”du”匹配,expression与(display “3 > 2”)匹配,而condition与(> 2 3)匹配。匹配成功,所以这个模式对应的模板被展开为(if (not (> 2 3)) (display “3 > 2”))。执行该语句,便导致“3 > 2”被打印出来。两行程序,我们便可以体验新的编程手段。还不够酷么?
    我们再看一个例子。Python和Haskell支持list comprehension,用类似集合定义的语句转换已知列表。比如下面的Python程序挑出从1到10里的奇数,并把将它们乘以2。最后的结果是[2, 6, 10, 14, 18]。                 [2 * x for x in range(10) if x % 2 == 1] Haskell里甚至支持对多个列表同时操作。下面的例子表示,依次取出列表[1, 3, 5]里的每个元素x,和列表[2, 4, 6]里的每个元素y, 把他们组对。得到的结果是新的列表[(1,2),(1,4),(1,6),(3,4),(3,6),(5,6)]                [(x,y) | x <- [1,3,5], y <- [2,4,6]] 我们用Scheme的宏可以如魔法般实现这样雅致的功能。Scheme类库Swindle里包含了花样繁复的list comprehension功能。我们这里只实现一个阳春版的 [12],用Philip Wadler提出的转换规则 [13]
    1991年Guy Lapalme给出了Common Lisp的lisp comprehension宏定义 [14]。熟悉Lisp的老大们可以看出Lisp的句法转换明显不如Scheme的方便简洁。下面是一些测试例子: 
     
    下面的是对这个宏的解释:
    • flat-map是一个简单函数。它和函数map功能类似。不过它会展平嵌套的列表。比如说(map (lambda (x) (list x)) ‘(1 2 3))的结果是((1) (2) (3)),而把map换成flat-map得到的结果是(1 2 3)。
    • (define-syntax list-of。。。定义了list comprehension的句法。当系统看见list-of时,就知道要执行list comprehension了。
    • (syntax-rules (<-))表示开始定义句法转换规则。关键词syntax-rules后紧跟的列表(<-)可以包含一个或多个标识符。Scheme在句法转换时会自动忽略这些标识符,不会让它们同随后的变量匹配。
    • 省略号…表示匹配0个或多个标识符号。比如,模式(x …)可以同(1)匹配,也可以同(1 2)匹配,也可以同(1 a 3)匹配。
    • 注意定义的句法可以递归出现,比如list-of 就出现在随后的定义里。正是递归的威力让看似复杂的list comprehension变得如此容易实现。也就是说,Scheme的宏其实是功能更为花哨的函数。
    这篇帖子不能涵盖Scheme的全部功能。比如我们完全没有涉及continuation,延迟计算,或者尾递归。不过希望你领略到Scheme玲珑剔透的设计。学会它(更重要的,享受它),你会发现,一条通向计算机技术伊甸园的秘密小道出现在你脚下。
     

    [1]  Alan Perlis, Epigrams of Programming, SIGPLAN Notices Vol. 17, No. 9(September 1982), pp7-13. Alan Perlis因为开发Algo编程语言获得1966年的图灵奖。Algo语言对命令式编程影响深远。流行多年的C,C++,和Pascal都属于Algo家族的成员。现下的热门Java和JavaScript虽然一个传承着Smalltalk的基因,一个根本就是Lisp的骨血,也要披着Algo家族句法风格的外衣。
    [2] John K. Federaro, Lisp Is Chameleon,  Communications of the ACM, Volume 34, Issue 9(September 1991), pp27,http://portal.acm.org/citation.cfm?doid=114669.114670 ACM很不厚道,看这篇文章需要ACM的帐户。
    [3] 号称是这篇文章说的:Howard Cannon, Flavors -- A Non-Hierarchical Approach to Object-oriented Programming. Unpublished draft, 1979, 1992, 2003。
    [4] AOP(Aspect Oriented Programming)这个名词是AspectJ小组最先提出来的,但AOP的一些基本功能,比如说before/after/around操作,早就在Gregor的CLOS里实现了。
    [5] John McCarthy, History of Lisp,  History of Programming Languages, 1978, pp173-185,http://www-formal.stanford.edu/jmc/history/lisp/lisp.html
    [6] John McCarthy, Recursive Functions of Symbolic Expression and Their Computation my Machine, http://www-formal.stanford.edu/jmc/recursive/recursive.html
    [7] Guy L. Steele, Richard P. Gabriel , Evolution of Lisp 1993.
    [8] Richard Kelsey et., Revised 5 Report On the Algorithmic Language Scheme, 1998, http://www.schemers.org/Documents/Standards/R5RS/r5rs.pdf
    [9] A Conversation With Alan Kay, ACM Queue, Vol 2, No. 9, Dec/Jan 2004 – 2005. http://acmqueue.com/modules.php?name=Content&pa=showpage&pid=273&page=1。Alan Kay真是人精。他的访谈向来精彩。强烈推荐。麦克斯韦方程组是詹姆斯.麦克斯韦19世纪末总结(非原创)出的一套方程组,精炼地描述了电场,磁场,电压,和电流间的关系。虽然方程组不过4个方程,却是经典电磁学的根基。
    [10] John Hughs,  Why Functional Programming Matters,  http://www.math.chalmers.se/~rjmh/Papers/whyfp.html。被众多老大推荐的经典论文。这篇文章出来,“黏合”的概念便广为传播。里面还有不少精彩例子,包括求解微分积分,和大小树剪枝。不是每个人都对数学感兴趣。而大小树的例子又太长。不然他们都值得细述。
    [11] 刘未鹏,《你应当如何学习C++》, http://blog.csdn.net/pongba/archive/2007/05/16/1611593.aspx
    [12] 我最早的实现要笨拙得多,幸好新闻组有人出手解惑: http://groups.google.com/group/comp.lang.scheme/browse_thread/thread/1fa9a460bdb3110f/9b310ada96a8d637?hl=en
    [13] Simon L Payton Jones,  Implementation of Functional Programming Languages, 1987, http://research.microsoft.com/~simonpj/papers/slpj-book-1987/index.htm 规则在第7章。下面是代码对应的转换规则: flat-map f [] = [] flat-map f (x: xs) = (f x) ++ (flat-map f xs) (a) TE[[E | v<- L: Q]] = flat-map (lambda (v). TE[[E | Q]]) TE[L]
    (b) TE[[E|B; Q]] = if TE[B] TE[[E|Q]] NIL
    (c) TE[ [e |]] = Cons TE[E] NIL
    这里E是表达式, B是返回布尔值的表达式,L是列表,Q是一个或多个生成器或B,而v表示变量。这里是一个例子:
    TE( [x * x | x <- xs; x > 2])
    ð      flat-map (lambda x. TE([ x * x | x > 2])) xs
    ð      flat-map(lambda x. if (x > 2) then TE([x * x | ] ) nil) xs
    ð      flat-map(lambda x. if(x > 2) then (cons (x * x) nil) nil) xs
     
    [14] Guy Lapalme,  Implementation of Lisp Comprehension Macro, http://rali.iro.umontreal.ca/Publications/urls/LapalmeLispComp.pdf, 这篇论文里用了优化过的转换规则,并且通过修改reader macro, 让生成的宏识别通用的方括号[],而不是象我们例子那用函数名list-of。不过呢,Common Lisp的宏要求我们手工完成模板中代码的替换,所以随处可见准引号`, 取引号操作符,,和去引号兼列表剥除操作符,@。相比之下,Scheme的syntax-rule就清爽多了。人叫define-syntax而不是defmacro,并非浪得虚名。
    P.S.,  Jacky老大说这篇帖子不够生动,有妇联干部带三个表劝小夫妻不要离婚的严肃作派,并慷慨提供了范文。也一并帖在这里:
    scheme!知道的明白是无数括号堆砌起来的一门语言 ,不知道还以为是schema的同门师兄弟。于是有很多同行问 ,为什么要学习传说中这样诡异的语言。我的回答往往只有一字 :爽感!(在“爽”字被用烂了的一个环境下,爽感似乎更能表达我 的那种澎湃之情!)当大家看着你噼里啪啦在键盘上敲着一行行天书 ,眼中崇敬迷离的眼神对你葱白攀生到有3,4层楼那么一个高度的 时候,是不是那脆弱的虚荣心得到了极大的高潮?爽乎 !scheme 不仅可以让你成为更优秀的程序员,教你可以写出高效美妙的程序 ,还可以帮助我们理解计算的本质。当然如果你只是做业务方面的应 用,不需要任何算法,数学计算,只是需要在一些现成的架构里面填 充一些业务流程的话,看看前面下面部分介绍就足够了 ,当别人在吹嘘显摆的时候,你还不至于以为你到了火星。当然 ,如果你对scheme充满好奇,兴趣的话,那就太棒了 ,本篇文章就是领你进入这个神奇世界的台阶,做好准备 ,我们准备起飞。 3    1975年问世的Scheme是Lisp方言,所以我们不妨先吹 吹LISP。传说中,排资论辈,LISP和FORTRAN是都是 属于古老的语言,但是fortran经常作为反面教材 ,LISP相比之下那自然是无限风光,倍受世人赞誉 。LISP拥趸们不断“发现”Lisp里简单却深刻 ,浅显而强大的特性,并应用到不同地方,取得非凡成就 。牛牵到哪里都是牛,上下纵横50年,就比如说现在红的发紫的R uby,Python,和JavaScript语言 ,它们最为人称道的功能,竟然大多源于Lisp(后面会有例子说 明)。也许John K. Foderaro这位老牛的比喻和总结最能说明Lisp的价值 :Lisp好比变色龙,高度适应环境的改变,因为它是一门 *可以编程的编程语言*。我们不仅可以用Lisp编程 ,还可以对Lisp编程i。Lisp内置的抽象方法让Lisp程 序员们身段灵活,长袖善舞。每当新的编程范式出现 ,Lisp程序员们总能快速实现相关功能,甚至做出进一步改进。 ----比如Smalltalk展示面向对象编程的潜力后 ,MIT媒体实验室的Cannon Howard便在1982年推出Flavors,一个功能丰富的 面向对象扩展。Cannon的扩展不仅实现了 当时流行的面向对象功能,还首创了多继承,多重分派 ,以及被Ruby程序员狂赞的mixini。尔后Gregor Kiczales又在集大成的CLOS里加入现在颇为眩目的面向 方面(AOP)编程方法ii。---这段如果再比较说明和LIS P的关系的话,我想那就更好了。    LISP吹捧完了后,现在再说说Scheme的传奇故事 。1958年,John McCarthy从达特茅斯搬到MIT。当时人工智能的另一奠基 人Marvin Minsky也在MIT。牛人相见,好比姚麦组合,利刃相击 ,火花耀眼。著名的MIT人工智能项目在两人领导下上马i 。但是在研究AI的过程中,McCarthy需要一门编程语言表 达他的理论模型。而当时人见人爱的图灵机只有一套笨拙的语言 ,不适合干净利落地表达复杂的递归函数,于是乎需求产生了 ,McCarthy在丘齐的lambda算子基础上顺便就设计了 Lisp,其实最初Lisp是一个纯纯的理论工具 ,用来进行程序的推导和证明。实在需要用机器验证理论了 ,研究组的老大们就手工把Lisp程序翻译成IBM 740的汇编代码,再上载到IBM 740上运行。人肉编译器们甚至热衷于编译各式Lisp程序 ,觉得跟解智力题一样好玩儿。他们还证明了可以用Lisp写出一 个通用函数eval(), 用来解释执行Lisp的表达式i。但他们光顾赞叹eval( )和元图灵机一样彪悍,且比图灵机构造出元图灵机的代码美妙 ,并没想到eval就是一个通用的Lisp解释器。幸好有天McCarthy的学生S.R. Russell灵机闪现,连夜用IBM704的机器语言实现ev al()。于是世界上第一个Lisp解释器横空出世 ,人肉编译才渐渐失传。那时真是计算机科学研究的黄金时代啊 ,人们可以一夜之间改变世界,比居委会大妈在股市一夜暴富还来得 轻快。顺便提一下,我们习以为常的条件判断语句,也是McCar thy在Lisp里发明的。而为了让函数应用没有副作用和实现函 数闭包,McCarthy的研究小组又顺便发明了垃圾收集 。这些顺便发明的产物,那一样不是现在编程语言基石 ,当时要是随便一样跺跺脚,现在的编程格局估计都要中个几百下面 目全非脚。1975年,同是MIT的Gerald Jay Sussman和Guy Steels为了研究Carl Hewitt的面向对象模型,用Lisp编写了一个玩具语言 。这个玩具语言简化了当时流行的Lisp语法,引入了词法定界 (又叫静态范围)和Continuation两大革新 。Sussman和Steels给这门语言取名Schemer ,希望它发展成像AI里著名系统Planner一样的有力工具 。可惜当时MIT用的操作系统ITS只允许最长6个字节的文件名 。Sussman和Steels不得不把Schemer的最后一 个字幕’r’去掉。Scheme问世便显露峥嵘:Sussman 和Steels很快发现Scheme的函数和Hewitt模型里 的演员(也就是我们现在所谓的对象)没有本质区别 ,连句法都基本一致。事实上,Sussman在教材 《计算机程序设计与解释》的第二章用短短几十行代码展示了一套面 向对象系统。    好了,正餐现在开始。Scheme是极度简化的语言 。他的规范文档不过47页i,浓缩的就是极品,惊讶吧 。相比另一大Lisp分支Common Lisp规范的上 千页文档或者Java规范的500来页文档,可见Scheme的 短小精悍。不过,我们可以用Scheme写出优雅犀利的程序 。Scheme规范R5RS开篇道出了Scheme的设计宗旨 :设计编程语言时不应堆砌功能,而应去掉让多余功能显得必要的弱 点和限制。Smalltalk的发明人Alan Kay在一次访谈录中提到,Lisp是编程语言中的麦克斯韦方程 组ii。这句评价用到Scheme上更为合适。Scheme甚至 让我们写出用其他语言无法写出的程序。这个时候,一般是老大们轻 蔑抛出编程语言图灵完备这种论点的时候。所以俺不妨小小地提醒一 下:理论上,理论和实践没有差别,但实际上两者差别海了去了 。不然,我们干嘛不继续用机器语言编程呢?Scheme写出的程 序用汇编/C也能实现,不过这样想的老大们最好先用汇编 /C写出一个Scheme的解释器。Sussman和Steel es用Scheme探索不同的编程模型时时,往往一周做出十来种 不同的解释器,可以旁证Scheme的简洁和灵活 。在解释是什么造就了Scheme的精练与生猛之前 ,我们先介绍一下Scheme的基本元素:

    展开全文
  • Scheme 初步

    2017-07-26 10:19:24
    本文只涉及 Scheme 里最基础的一些语法部分,要是恰好能够帮助到后来的学习者入门 Scheme,那更是再好不过。 为什么选择学 Scheme 三方面的原因。 首先是自己基本上对函数式语言的接触为零。平时工作和...

    之前定了每年学习一门语言的目标,自然不能轻言放弃。今年目标:简单掌握 Scheme。

    因为自己接触这门语言也不过寥寥数天,所以更多的会以引导的方式简单介绍语法,而不会 (也没有能力) 去探讨什么深入的东西。本文很多例程和图示参考了紫藤貴文的《もうひとつの Scheme 入門》这篇深入浅出的教程,这篇教程现在也有英译版中译版。我自己是参照这篇教程入门的,一方面这篇教程可以说是能找到合适初学者学习的很好的材料,另一方面也希望能挑战一下自己的日文阅读能力 (结果对比日文和英文看下来发现果然还是日文版写的比较有趣,英文翻译版本就太严肃了,而中文版感觉是照着英文译版二次翻译的,有不少内容上的缺失和翻译生硬的问题,蛮可惜的)。因为中文的 Scheme 的资料其实很少,所以顺便把自己学习的过程和一些体会整理记录下来,算是作为备忘。本文只涉及 Scheme 里最基础的一些语法部分,要是恰好能够帮助到后来的学习者入门 Scheme,那更是再好不过。

    为什么选择学 Scheme

    三方面的原因。

    首先是自己基本上对函数式语言的接触为零。平时工作和自己的开发中基本不使用函数式编程,大脑已经被指令式程序占满,有时候总显得不很灵光。而像 Swift 这样的语言其实引入了一些函数式编程的可能性。多接触一些函数式的语言,可能会对实际工作中解决某些问题有所帮助。而 Scheme 比起另一门常用 Lisp 方言 Common Lisp 来说,要简单不少。比较适合像我这样非科班出身,CS 功力不足的开发者。

    其次,Structure and Interpretation of Computer Programs (SICP) 里的例程都是使用 Scheme 写的。虽然不太有可能有时间补习这本经典,但是如果不会一点 Scheme 的话,那就完全没有机会去读这本书了。

    最后,Scheme 很酷也很好玩,虽然在实际中可能并没有鸟用,但是和别人说起来自己会一点 Scheme 的话,那种感觉还是很棒的。

    其实还有一点对 hacker 们很重要,如果你喜欢使用像 Emacs 这样的基于 Lisp 的编辑器的话,使用 Scheme 就可以与它进行交互或者是扩展它的功能了。

    那让我们尽快开始吧。

    成为 Scheme Hacker 的第一步

    成为 Scheme Hacker 的第一步,当然是安装和配置运行环境,同时这也是最难跨过去的一步。想想有多少次你决心学习一门新语言的时候,在配置好开发环境后就再也没有碰过吧。所以我们需要尽快跨过这个步骤。

    最简单的开发环境点击这个链接,然后你就可以开始用 Scheme 编程了。如果你更喜欢在本地环境和终端里操作的话,可以下载 MIT/GUN Scheme。在 OS X 上解包后是一个 .app 文件,运行 .app 包里的 /Contents/Resources/mit-scheme 就可以打开一个解释器了。

    Hello 1 + 1

    虽然大部分语言都是从 Hello World 开始的,但是对于 Scheme 来说,计算才是它的强项。所以我们从 1 + 1 开始。计算 1 + 1 程序在 Scheme 中是这样的:

    1 ]=> (+ 1 1)
    
    ;Value: 2
    
    1 ]=>
    

    1 ]=> 是输入提示符,我们输入的内容是 (+ 1 1),得到的结果是 2。虽然语句很简单,但是这里包含了 Scheme 的最基本的语素,有三个地方值得特别注意。

    1. 成对的括号。一对括号表示的是一步计算,这里 (+ 1 1) 表示的就是 1 + 1 这一步运算。
    2. 紧接括号的是函数名字,再然后是函数的参数。在这里,函数名字就是 “+”,两个 1 是它的参数。Scheme 中大部分的运算符其实都是函数。
    3. 使用空格,tab 或是换行符来分割函数名以及参数。

    和别的很多语言一样,Scheme 在函数调用时也有计算优先级,会先对输入的参数进行计算,然后再进行函数调用。还是以上面的 1 + 1 为例。首先解释器看到加号,但是此时运算并没有开始。解释器会先计算第一个参数 1 的值 (其实就是 1),然后计算第二个参数 1 的值 (其实还是 1)。然后再用两个计算得到的值来进行加法运算。

    另外,”+” 这个函数不仅可以接受两个参数,其实它是可以接受任意多个参数的。比如 (+) 的结果是 0,(+ 1 2 3 4) 的结果是 10。

    学会加法以后,乘法自然也不在话下了。

    1 ]=> (* 2 3)
    
    ;Value: 6
    
    1 ]=>
    

    减法和除法稍微不同一些,因为它们并不满足交换律,所以可能会有疑问。但是只要记住参数是平等的,它们会顺次计算就可以了。举个例子:

    1 ]=> (- 10 5 3)
    
    ;Value: 2
    
    1 ]=> (/ 20 2 2)
    
    ;Value: 5
    

    对于除法,有两个需要注意的地方。首先和我们熟悉的很多语言不同,Scheme 是默认有分数的概念的。比如在 C 系语言中,如果只是在整数范围的话,我们计算 10 / 3 的结果会是 3;如果是浮点型的话结果为 3.33333。而在 Scheme 中,结果是这样的:

    1 ]=> (/ 10 3)
    
    ;Value: 10/3
    

    这是一个分数,就是三分之十,绝对精确!

    另一个需要注意的是,如果 / 只有一个输入的话,它的意思是取倒数。

    1 ]=> (/ 2)
    
    ;Value: 1/2
    

    如果你需要一个浮点数而不是分数的话,可以使用 exact->inexact 方法,将精确值转为非精确值:

    1 ]=> (exact->inexact (/ 10 3))
    
    ;Value: 3.3333333333333335
    

    Scheme 也内建定义了一些其他的数学运算符号,如果你感兴趣,可以查看 R6RS 的相关章节

    R6RS (Revisedn Report on the Algorithmic Language Scheme, Version 6) 是当前的 Scheme 标准。

    定义变量和方法,Hello World

    通过简单的 1 + 1 运算我们可以大概知道 Scheme 中的奇怪的括号开头的意思了。有了这个作为基础,我们可以来看看如何定义变量和方法了。

    Scheme 中通过 define 来定义变量和方法:

    ; s 是一个变量,值为 "Hello World"
    (define s "Hello World")
    
    ; f 是一个函数,它不接受参数,调用时返回 "Hello World"
    (define f (lambda () "Hello World"))
    

    上面的 lambda 可以生成一个闭包,它接受两个参数,第一个是一个空的列表 (),表示这个闭包不接受参数;第二个是 “Hello World” 这个字符串。在解释器中定义好两者之后,就可以进行调用了:

    1 ]=> (define s "Hello World")
    
    ;Value: s
    
    1 ]=> (define f (lambda () "Hello World"))
    
    ;Value: f
    
    1 ]=> s
    
    ;Value 24: "Hello World"
    
    1 ]=> f
    
    ;Value 25: #[compound-procedure 25 f]
    
    1 ]=> (f)
    
    ;Value 26: "Hello World"
    

    既然我们已经知道了 lambda 的意义和用法,那么定义一个接受参数的函数也就不是什么难事了。比如上面的 f,我们想要定义一个接受名字的函数的话:

    1 ]=> (define hello
            (lambda (name)
                (string-append "Hello " name "!")
            )
          )
    
    ;Value: hello
    
    1 ]=> (hello "onevcat")
    
    ;Value 27: "Hello onevcat!"
    

    很简单,对吧?其实甚至可以更简单,define 的第一个参数可以是一个列表,其中第一个元素是函数名名字,之后的是参数列表。

    用专业一点的术语来说的话,就是 define 的第一个参数是一个 cons cell 的话,它的 car 是函数名,cdr 是参数。关于这些概念我们稍后再仔细说说。

    于是上面的方法可以简单地写作:

    1 ]=> (define (hello name)
            (string-append "Hello " name "!"))
    
    ;Value: hello
    
    1 ]=> (hello "onevcat")
    
    ;Value 28: "Hello onevcat!"
    

    光说不练假把式,所以留个小练习给大家吧,用 define 来定义一个函数,让其为输入的数字 +1。如果你无压力地搞定了的话,我们就可以继续看看 Scheme 里的条件语句怎么写了。

    条件分支和布尔逻辑

    不论是什么编程语言,条件分支或者类似的概念应该都是不可缺少的部分。在 Scheme 中,使用 if 可以进行条件分支的处理。和其他很多语言不一样的地方在于,函数式语言中函数才是一等公民,if 的行为也和一个其他的普通函数很相似,是作为一个函数来使用的。它的语法是:

    (if condition ture_action false_action)
    

    与普通函数先进行输入的取值不同,if 将会先对 condition 运算式进行取值判断。如果结果是 true (在 Scheme 中用 #t 代表 true,#f 代表 false),则再对 ture_action 进行取值,否则就执行 false_action。比如我们可以实现一个 abs 函数来返回输入的绝对值:

    1 ]=> (define (abs input)
            (if (< input 0) (- input) input))
    
    ;Value: abs
    
    1 ]=> (abs 100)
    
    ;Value: 100
    
    1 ]=> (abs -100)
    
    ;Value: 100
    

    也许你已经猜到了,Scheme 的布尔逻辑也是遵循函数式的,最常用的就是 and 和 or 两种了。和常见 C 系语言类似的是,and 和 or 都会将参数从左到右取值,一旦遇到满足停止条件的值就会停止。但是和传统 C 系语言不同,布尔逻辑的函数返回的不一定就是 #t 或者 #f,而有可能是输入值,这和很多脚本语言的行为是比较一致的:and 会返回最后一个非 #f 的值,而 or 则返回第一个非 #f 的值:

    1 ]=> (and #f 0)
    
    ;Value: #f
    
    1 ]=> (and 1 2 "Hello")
    
    ;Value 13: "Hello"
    
    1 ]=> (or #f 0)
    
    ;Value: 0
    
    1 ]=> (or 1 2 "Hello")
    
    ;Value: 1
    
    1 ]=> (or #f #f #f)
    
    ;Value: #f
    

    在很多时候,Scheme 中的 and 和 or 并不全是用来做条件的组合,而是用来简化一些代码的写法,以及为了顺次执行一些代码的。比如说下面的函数在三个输入都为正数的情况下返回它们的乘积,可以想象和对比一下在指令式编程中同样功能的实现。

    (define (pro3and a b c)
        (and (positive? a)
            (positive? b)
            (positive? c)
            (* a b c)
        )
    )
    

    除了 if 之外,在 C 系语言里另一种常见的条件分支语句是 switch。Scheme 里对应的函数是 condcond 接受多个二元列表作为输入,从上至下依次判断列表的第一项是否满足,如果满足则返回第二项的求值结果并结束,否则一直继续到最后的 else

    (cond
      (predicate_1 clauses_1)
      (predicate_2 clauses_2)
        ......
      (predicate_n clauses_n)
      (else        clauses_else))
    

    在新版的 Scheme 中,标准里加入了更多的流程控制的函数,它们包括 beginwhen 和 unless 等。

    begin 将顺次执行一系列语句:

    (define (foo)
      (begin
        (display "hello")
        (newline)
        (display "world")
      )
    )
    

    when 当条件满足时执行一系列代码,而 unless 在条件不满足时执行一系列代码。这些改动可以看出一些现代脚本语言的特色,但是新的标准据说也在 Scheme 社区造成了不小争论。虽然结合使用 ifand 和 or 肯定是可以写出等效的代码的,但是这些额外的分支控制语句确实增加了语言的便利性。

    循环

    一门完备的编程语言必须的三个要素就是赋值,分支和循环。前两个我们已经看到了,现在来看看循环吧:

    do

    1 ]=> (do ((i 0 (+ i 1))) ; 初始值和 step 条件
              ((> i 4))       ; 停止条件,取值为 #f 时停止
            (display i)       ; 循环主体 (命令)
          )
    01234
    ;Value: #t
    

    唯一要解释的是这里的条件是停止条件,而不是我们习惯的进入循环主体的条件。

    递归

    可以看出其实 do 写起来还是比较繁琐的。在 Scheme 中,一种更贴合语言特点的写法是使用递归的方式来完成循环:

    1 ]=> (define (count n)
              (and (display (- 4 n))
                   (if (= n 0) #t (count (- n 1)))
              )
          )
    
    ;Value: count
    
    1 ]=> (count 4)
    01234
    ;Value: #t
    

    列表和递归

    也许你会说,用递归的方式看起来一点也不简单,甚至代码要比上面的 do 的版本更难理解。现在看来确实是这样的,那是因为我们还没有接触 Scheme 里一些很独特的概念,cons cell 和 list。我们在上面介绍 define 的时候曾经提到过,cons cell 的 car 和 cdr。结合这个数据结构,Scheme 里的递归就会变得非常好用。

    那么什么是 cons cell 呢?其实没有什么特别的,cons cell 就是一种数据结构,它对应了内存的两个地址,每个地址指向一个值。

    要初始化一个上面图示的 cons cell,可以使用 cons 函数:

    1 ]=> (cons 1 2)
    
    ;Value 13: (1 . 2)
    

    我们可以使用 car 和 cdr 来取得一个 cons cell 的两部分内容 (car 是 “Contents of Address part of Register” 的缩写,cdr 是 “Contents of Decrement part of Register”):

    1 ]=> (car (cons 1 2))
    
    ;Value: 1
    
    1 ]=> (cdr (cons 1 2))
    
    ;Value: 2
    

    cons cell 每个节点的内容可以是任意的数据类型。一种最常见的结构是 car 中是数据,而 cdr 指向另一个 cons cell:

    上面这样的数据结构对应的生成代码为:

    1 ]=> (cons 3 (cons 1 2))
    
    ;Value 14: (3 1 . 2)
    

    有一种特殊的 cons cell 链,其最后一个 cons cell 的 cdr 为空列表 '(),这类数据结构就是 Scheme 中的列表。

    对于列表,我们有一种更简单的创建方式,就是类似 '(1 2 3) 这样。对于列表来说,它的 cdr 值是一个子列表:

    1 ]=>  '(1 2 3)
    
    ;Value 15: (1 2 3)
    
    1 ]=> (car '(1 2 3))
    
    ;Value: 1
    
    1 ]=> (cdr '(1 2 3))
    
    ;Value 16: (2 3)
    

    而循环其实质就是对一列数据进行处理的过程,结合 Scheme 列表的特性,我们意识到如果把列表运用在递归中的话,car 就是遍历的当前项,而 cdr 就是下一次递归的输入。Scheme 和递归调用可以说能配合得天衣无缝。

    比如我们定义一个将列表中的所有数都加上 1 的函数的话,可以这么处理:

    (define (ins_ls ls)
        (if (null? ls)
          '()
          (cons (+ (car ls) 1) (ins_ls (cdr ls)))
        )
    )
    
    (ins_ls '(1 2 3 4 5))
    
    ;=> (2 3 4 5 6)
    

    尾递归

    递归存在性能上的问题,因为递归的调用需要在栈上保持,然后再层层返回,这会造成很多额外的开销。对于小型的递归来说还勉强可以接受,但是对于递归调用太深的情况来说,这显然是不可扩展的做法。于是在 Scheme 中对于大型的递归我们一般会倾向于将它写为尾递归的方式。比如上面的加 1 函数,用尾递归重写的话:

    (define (ins_ls ls)
        (ins_ls_interal ls '()))
    
    (define (ins_ls_interal ls ls0)
        (if (null? ls)
            ls0
            (ins_ls_interal (cdr ls) (cons ( + (car ls) 1) ls0))))
    
    (define (rev_ls ls)
      (rev_ls_internal ls '()))
    
    (define (rev_ls_internal ls ls0)
      (if (null? ls)
          ls0
          (rev_ls_internal (cdr ls) (cons (car ls) ls0))))
    
    (rev_ls (ins_ls '(1 2 3 4 5)))
    
    ;=> (2 3 4 5 6)
    

    函数式

    上面介绍了 Scheme 的最基本的赋值,分支和循环。可以说用这些东西就能够写出一些基本的程序了。一开始会比较难理解 (特别是递归),但是相信随着深入下去和习惯以后就会好很多。到现在为止,除了在定义函数时,其实我们还没有直接触碰到 Scheme 的函数式特性。在 Scheme 里函数是一等公民,我们可以将一个函数作为参数传给另外的函数并进行调用,这就是高阶函数。

    一个最简单的例子是排序的时候我们可以将一个返回布尔值的函数作为排序规则:

    1 ]=> (sort '(7883 9099 6729 2828 7754 4179 5340 2644 2958 2239) <)
    
    ;Value 13: (2239 2644 2828 2958 4179 5340 6729 7754 7883 9099)
    

    更甚于我们可以使用一个匿名函数来控制这个排序,比如按照模 100 之后的大小 (也就是数字的后两位) 进行排序:

    1 ]=> (sort '(7883 9099 6729 2828 7754 4179 5340 2644 2958 2239)
          (lambda (x y) (< (modulo x 100) (modulo y 100))))
    
    ;Value 14: (2828 6729 2239 5340 2644 7754 2958 4179 7883 9099)
    

    类似这样的特性在一些 modern 的语言里并不算罕见,但是要知道 Scheme 可是有些年头的东西了。类似的还有 mapfilter 等。比如上面的 list 加 1 的例子,用 map 函数就可以非常简单地实现:

    (map (lambda (x) (+ x 1)) '(1 2 3 4 5))
    
    ;=> (2 3 4 5 6)
    

    接下来…

    篇幅有限,再往长写的话估计没什么人会想看完了。到这里为止关于 Scheme 的一些基础内容也算差不多了,大概阅读最简单的 Scheme 程序应该也没有太大问题了。在进一步的学习中,如果出现不认识的函数或者语法的话,可以求助 SRFI 下对应的文档或是在 MIT/GNU Scheme 文档中寻找。

    本文一开始提到的教程很适合入门,之后的话可以开始参看 SICP,可以对程序设计和 Scheme 的思想有更深的了解 (虽然阅读 SICP 的目的不应该是学 Scheme,Scheme 只是帮助你进行阅读和练习的工具)。因为我自己也就是个愣头青的初学者,所以无法再给出其他建议了。如果您有什么好的资源或者建议,非常欢迎在评论中提出。

    另外,相比起 Scheme,如果你想要在实际的工程中使用 Lisp 家族的语言的话,Racket 也许会是更好的选择。相比于面向数学和科学计算来说,Racket 支持对象类型等概念,更注重在项目实践方面的运用。

    就这样吧,我要继续去和 Scheme 过周末了。

    展开全文
  • scheme 之道

    2015-03-30 21:50:31
    这是一篇 Scheme 的介绍文章. Scheme 是一个 LISP 的方言, 相对于 Common LISP 或其他方言, 它更强调理论的完整和优美, 而不那么强调实用价值. 我在 学习 Scheme 的时候, 常想的不是 "这有什么用
  • Scheme语言入门

    千次阅读 2019-05-17 10:42:28
    4.6.1.Scheme语言入门 最早听说 LISP,是 Stallman 的 GNU Emacs 中将 LISP 作为嵌入语言,定制和增强 Emacs。GNU Emacs 是一个文本编辑器,文本就是一种符号,而 Lisp 正好就是针对符号计算发明的,因此在GNU ...
  • scheme 之门

    2018-12-20 09:33:00
    scheme 之门   开始之前   这是一篇 Scheme 的介绍文章. Scheme 是一个 LISP 的方言, 相对于 Common LISP 或其他方言, 它更强调理论的完整和优美, 而不那么强调实用价值. 我在 学习 Scheme 的时候, 常想的不是 &...
  • 开始学习Scheme

    2018-12-21 09:22:00
    开始学习Scheme 函数式编程(Functional Programming)是在MIT研究人工智能(Artificial Intelligence)时发明的,其编程语言为Lisp。确切地说,Lisp是一个语言家族,包括无数的方言如:Scheme、Common Lisp、...
  • Scheme共舞

    2014-10-26 01:27:56
    Scheme共舞 分类: 计算机科学 开发 八卦2007-07-03 13:49 26785人阅读 评论(19) 收藏 举报 schemelisp编程语言lambdasmalltalk  发表在《程序员》2007年7月刊上。不log上写帖子不用考虑版面...
  • [scheme-001] 最简scheme教程 001

    万次阅读 2018-04-14 22:32:28
    1.参考资料the scheme programming language 4ththe little schemer2. scheme诞生于1975年,是一种lisp方言。通用型语言,用途遍及it领域。3.关于变量变量名的第一个字母不能是"@"符号,不能是数字,不能...
  • Scheme 语言介绍

    千次阅读 2006-09-02 19:07:00
    Scheme 语言介绍Wolfgang Kreutzer翻译:寒蝉退士原文:http://www.cosc.canterbury.ac.nz/~wolfgang/cosc302/Chap2.3.html译者声明:译者对译文不做任何担保,译者对译文不拥有任何权利并且不负担任何责任和义务。...
  • Scheme 序列和树

    2020-02-04 20:21:38
    可以使用简写为, (list 1 2 3 4) 函数 取n号元素 (define (list-ref items n) (if (= n 0) (car items) (list-ref (cdr items) (- n 1)))) 求表长 (define (length items) (if (null? items) 0 ...
  • Yet Another Scheme Tutorial Scheme入门教程 Takafumi Shido著 DeathKing,lincode译 出处,http://deathking.github.io/yast-cn/ ------------------ 简介 这是一本面向初学者的温和且循...
  • Chez Scheme 的传说

    2013-11-22 13:43:04
    Chez Scheme 的传说 在上一篇博文的最后,我提到了 Lisp 编译器的问题。由于早期的 Lisp 编译器生成的代码效率普遍低下,成为了 Lisp 失败的主要原因之一。而现在的高性能 Lisp 编译器(比如 Chez Scheme),其实...
  • 计算机语言中x x,Scheme

    2021-07-17 00:17:57
    Scheme(计算机程序语言)语音编辑锁定讨论上传视频Scheme 编程语言是一种Lisp方言,诞生于1975年,由 MIT 的 Gerald J. Sussman 和 Guy L. Steele Jr. 完成。它是现代两大Lisp方言之一;另一个方言是Common Lisp。...
  • MIT/GNU Scheme用户手册(一)

    千次阅读 2013-05-18 21:49:33
    MIT/GNU Scheme用户手册 介绍 本文档描述了如何安装和使用MIT/GNU Scheme,一个非Common的LISP变种。 本文档 给出了所有支持平台下的Scheme安装指导;关于命令行参数和控制Scheme工作的环境变量的完整文档;...
  • scheme-谈语法

    2019-07-16 21:45:03
    https://henix.github.io/feeds/yinwang/2013-03-08-on-syntax.html
  • Scheme 程序语言介绍之一

    千次阅读 2014-01-14 23:28:36
    Scheme 是差不多三十年前诞生在 MIT 人工智能实验室的一门程序语言。它是 Lisp 语言的发展。今天的 Scheme 在程序语言的理论研究和工程应用两方面都发挥着持久和越来越重要的影响。本文作者在 IBM developerWorks ...
  • Scheme非完整笔记

    2008-03-06 17:33:20
    Scheme的条件表达式里,只有#f才被认为是假,而其它对象都被认为是真。所以3, (), "false", nil都是真。 [code](DEFINE (test a) (COND ((EVAL a) (DISPLAY "true")) (ELSE (DISPLAY "false")) ) ) (test #f) ...
  • Scheme 自学教程 1

    千次阅读 2007-11-14 03:56:00
     关于最后一句Scheme脚本的解释是:在一个需要布尔类型的上下文环境中,Scheme把所有不是#f的值转化为true。  2.1.2数字类型  Scheme语言中的数字类型可以是整数(比如,42),分数(22/7),实数(3.1416)...
  • Scheme R5RS 4.表达式

    2016-08-24 19:20:24
    表达式类型分为基本类型和派生类型两种. 1. 基本表达式类型包括变量和过程调用....常量表达式(quote 〈datum〉) 可简写为 ‘〈datum〉 数值常量,字符常量,字符串常量和布尔常量的值是”它们自身”. 〈datu

空空如也

空空如也

1 2 3 4 5 ... 20
收藏数 8,954
精华内容 3,581
关键字:

scheme缩写