热门标签 | HotTags
当前位置:  开发笔记 > 编程语言 > 正文

Lisp语言的后代,Scheme语言深入浅出

文章标题:Lisp语言的后代,Scheme语言深入浅出。Linux是中国IT实验室的一个技术频道。包含桌面应用,Linux系统管理,内核研究,嵌入式系统和开源等一些基本分类

  一、关于符号类型
  符号类型又称引用类型,在概要一文中本人介绍得非常的模糊,使很多初学者不理解。符号类型在Scheme语言中是最基础也是最重要的一种类型,这是因为Scheme语言的祖先Lisp语言的最初目的就是符号处理,在Scheme语言中几乎所有的东西都可以看做是符号或做为符号列表来处理,这也是我们把符号类型做为第一个问题研究的原因。
  
  与符号类型相关的关键字有四个,分别是:quote, quasiquote, unquote和unquote-splicing,如下所示:
  
  规范用法:(quote obj)
  简化用法:'obj (注意,'为右单引号,"双引号下面的那个符号。)
  意义:符号类型的定义,(quote obj)本身就是一个值,虽然它不如数字123这样直观。
  
  规范用法:(quasiquote obj)
  简化用法:`obj (注意,`为左单引号,~波浪号下面的那个符号。)
  意义:"类似符号"类型的定义,最好称之为逆符号类型,它可以将符号类型转换为具有实际意义的东西。
  
  规范用法:(unquote obj)
  简化用法:,obj (注意,,逗号,<小于号下面的那个符号。)
  意义:"非符号"类型的定义,非符号类型出现在符号类型或逆符号类型定义中间,它不直接做为符号类型使用,而是将运算结果做为符号类型的一部分。
  
  规范用法:(unquote-splicing obj)
  简化用法:,@obj
  意义:非符号类型的拼接,注意:,@ 两个符号做为一个操作符来使用)。当非符号类型是一些复杂算法时,需要用它来做一下拼接,以达到符号类型的目的。
  上面所说的所有规范用法和简化用法的功能都是相同的。
  
  符号类型的意义在于,一个说明,英文单词zebra指的是活生生的斑马,而'zebra或(quote zebra)指的是由字母z、e、b、r、a构成的这串符号(不是字符串),就象我们定义变量(define x 100),这时x指的就是100这个数值,而'x或(quote x)则代表字母x构成的这个符号。
  
  首先看一段代码:
  
  guile> (define s '(good morning))
  guile> s
  (good morning)
  guile> (symbol? s)
  #f
  guile> (list? s)
  #t
  guile> (symbol? (list-ref s 1))
  #t
  
  从此示例中可以看出,用quote定义的列表的类型仍是列表,而列表中的某一值的类型则是符号类型。还可以看出有点类似于如下:
  
  (+ 1 (+ 2 (+ 3 (+ 4 5)))) ==> (+ 1 2 3 4 5)
  (list 'a 'b 'c 'd 'e)   ==> '(a b c d e)
  
  两者有异曲同工之妙,减少了多余的操作符,使表达式更直观,更容易理解。
  
  从 '(1 2 3 4 5) ==> (1 2 3 4 5) 可以看出,由符号类型的定义来形成列表,这是Scheme语言继承自LISP语言的传统。
  
  下面是在guile中的用法示例:
  
  guile> `(1 ,(+ 1 1) 3)
  (1 2 3)
  guile> (quasiquote (1 (unquote (+ 1 1)) 3))
  (1 2 3)
  ;;;第一个是简化用法,第二个是标准用法。
  
  guile> `(1 ,@(map + '(1 3) '(2 4)) 9)
  (1 3 7 9)
  guile> (quasiquote (1 (unquote-splicing (map + (quote (1 3)) (quote (2 4)))) 9))
  (1 3 7 9)
  ;;;第一个是简化用法,第二个是标准用法(注意:,@ 两个符号做为一个操作符来使用)。
  
  从示例中我们可以看出,这些应用多数与列表有关,而处理列表是Scheme语言的关键所在。符号类型的用法对深入理解Scheme语言也非常关键,因为Scheme语言本身就可以理解为是这种符号类型的列表,处理符号类型就是处理Scheme语言本身。
  
  二、关于尾递归
  数列问题是研究递归的非常好的范例,在王垠的主页中有关于用菲波那契数列来说明非尾递归与尾递归之间区别和尾递归的好处的一个例子,见 http://learn.tsinghua.edu.cn/homepage/2001315450/wiki/TailRecursion.html 。我们这里用更简单一点的问题,求累计的问题来说明,即求自然数1+2+3+4+ ... +n的和。事实上就是设计一个过程,给它一个参数n,求1+2+3+ ... +n的和,我们首设计一个suma过程,代码如下:
  
  #! /usr/local/bin/guile -s
  !#
  
  (define suma
  (lambda (n)
  (if (= n 1)
  1
  (+ n (suma (- n 1))))))
  
  (display "(suma 100) ==> ")
  (display (suma 100)) (newline)
  
  (display "(suma 818) ==> ")
  (display (suma 818)) (newline)
  运行这段代码,会出现如下结果:
  (suma 100) ==> 5050
  (suma 818) ==> 334971
  
  说明:
  以(suma 5)为例,表达式展开后:
  
  (suma 5)
  (+ 5 (suma 4))
  (+ 5 4 (suma 3))
  (+ 5 4 3 (suma 2))
  (+ 5 4 3 2 (suma 1))
  (+ 5 4 3 2 1) ==> 15
  
  如果n为1000的话则会最终形成(+ 1000 999 ... 3 2 1)这样长度惊人的表达式,结果直接导致guile的崩溃。
  
  为什么会是818呢?因为819是会溢出的,出错,得不到结果,这可能大出乎我们意料之外,因为如果用C来写这样功能的程序代码,可能会求到6位数而不出问题。这一过程是用非尾递归来完成的,它的扩张呈指数级增长。代码的迅速膨胀,使guile没有处理到1000就崩溃了。
  
  我们再来看看采用尾递归的情况,代码如下:
  
  #! /usr/local/bin/guile -s
  !#
  
  (define sumb
  (lambda (n)
  (let f ((i n) (a 1))
  (if (= i 1)
  a
  (f (- i 1) (+ a i))))))
  
  (display "(sumb 100) ==> ")
  (display (sumb 100)) (newline)
  
  (display "(sumb 1000) ==> ")
  (display (sumb 1000)) (newline)
  
  运行结果如下:
  
  (sumb 100) ==> 5050
  (sumb 1000) ==> 500500
  还是以n为5的情况来说明:
  (sumb 5)
  (f 5 1)
  (f 4 6)
  (f 3 10)
  (f 2 13)
  (f 1 15) ==> 15
  
  这样的话,始终是依次计算,不会出现列表膨胀,所以n为1000时也不会出错,计算速度也很快。
  
  此结果大超出了非尾递归的818限制,参数是10000也没问题,因它采用尾递归,代码根本没有膨胀,而是依次计算。首先是在过程内部绑定了一个过程f,它有两个参数,一个i的值来自sum过程的参数n,另一个参数a定义值为1,当i值不等于时,仍调用f,第一个参数(也就是i)减1,第二个参数(也就是a)的值在原来的基础上加上i,当i的值为1是返回a,也就此sum过程的结果。理解这些后,你会发现事实上尾递归是在过程的绑定和过程的参数上做文章,用参数来保存运算结果,递归调用绑定的过程,最终达到运算目的。
  
  三、关于过程参数的问题
  过程的多参数问题对初学者不太好理解,一般情况下我们处理过程时,过程参数的数量是固定的,当过程的参数数量不固定时怎么办呢?对了,时刻记住列表,把过程的参数做为一个列表来处理,如求和过程:(sum arg1 arg2 ...),(初学者可能对如何实现定义这样的过程无从下手不知所措),我们如何来求这些参数的和呢?看下面的代码:
  
  guile> (define sum (lambda args (apply + args)))
  guile> sum
  #
  guile> (sum 1 2 3 4 5)
  15
  
  从中可以看出,lambda的格式有所变化,由原来的((lambda arg1 arg2 ...) body ...)变成了(lambda args body ...),也就是将原来的多个项组成的列表,改成了用一个名称args来标识的列表,其本质并没有变,变的是我们的方法,由原来的单项处理变成了统一处理的列表。
  
  这里还用到了for-each过程,通过下面代码来看一下for-each过程的一般用法:
  
  guile> (define newdisplay (lambda (x) (begin (display x)(newline))))
  guile> newdisplay
  #
  guile> (define tt (lambda args (for-each newdisplay args)))
  guile> tt
  #
  guile> (tt 'abc 'efg 'tomson)
  abc
  efg
  tomson
  
  for-each过程的一般用法是(for-each 过程 列表),此中的过程可以是我们自定义的,也可以是系统提供的,还可以是lambda 表达式。
  
  栈结构是一种简单而又有意义的数据结构,我们可以用列表来模拟一个简单的栈,下面是代码:
  
  #! /usr/local/bin/guile -s
  !#
  
  (define stack '())
  
  (define push!
  (lambda (x)
  (set! stack (cons x stack))))
  
  (define pop!
  (lambda ()
  (let ((temp (car stack)))
  (set! stack (cdr stack))
  temp)))
  
  (push! 9)
  (push! 8)
  (push! 7)
  (display stack) (newline)
  (display (pop!)) (newline)
  (display stack) (newline)
  
  结果如下:
  
  (7 8 9)
  7
  (8 9)
  
  这里面我们定义了一个变量stack来表示栈,定义一个过程push!向栈内压数据,同时还定义了一个过程pop!来从栈内弹出数据,完成了基本的栈功能。这段代码的缺点是要定义一个外部的变量来表示栈,同时还有两个过程,如果创建多个栈的话就需要更多的过程和变量了,这在某些情况下是不可想象的,如果程序中要用100个栈,我们就不得不100次复制和更改上面的代码。如何解决这一问题呢?看下面的代码:
  
  #! /usr/local/b
推荐阅读
  • 微软头条实习生分享深度学习自学指南
    本文介绍了一位微软头条实习生自学深度学习的经验分享,包括学习资源推荐、重要基础知识的学习要点等。作者强调了学好Python和数学基础的重要性,并提供了一些建议。 ... [详细]
  • Monkey《大话移动——Android与iOS应用测试指南》的预购信息发布啦!
    Monkey《大话移动——Android与iOS应用测试指南》的预购信息已经发布,可以在京东和当当网进行预购。感谢几位大牛给出的书评,并呼吁大家的支持。明天京东的链接也将发布。 ... [详细]
  • Lodop中特殊符号打印设计和预览样式不同的问题解析
    本文主要解析了在Lodop中使用特殊符号打印设计和预览样式不同的问题。由于调用的本机ie引擎版本可能不同,导致在不同浏览器下样式解析不同。同时,未指定文字字体和样式设置也会导致打印设计和预览的差异。文章提出了通过指定具体字体和样式来解决问题的方法,并强调了以打印预览和虚拟打印机测试为准。 ... [详细]
  • 本文介绍了lua语言中闭包的特性及其在模式匹配、日期处理、编译和模块化等方面的应用。lua中的闭包是严格遵循词法定界的第一类值,函数可以作为变量自由传递,也可以作为参数传递给其他函数。这些特性使得lua语言具有极大的灵活性,为程序开发带来了便利。 ... [详细]
  • Final关键字的含义及用法详解
    本文详细介绍了Java中final关键字的含义和用法。final关键字可以修饰非抽象类、非抽象类成员方法和变量。final类不能被继承,final类中的方法默认是final的。final方法不能被子类的方法覆盖,但可以被继承。final成员变量表示常量,只能被赋值一次,赋值后值不再改变。文章还讨论了final类和final方法的应用场景,以及使用final方法的两个原因:锁定方法防止修改和提高执行效率。 ... [详细]
  • GetWindowLong函数
    今天在看一个代码里头写了GetWindowLong(hwnd,0),我当时就有点费解,靠,上网搜索函数原型说明,死活找不到第 ... [详细]
  • 本文介绍了求解gcdexgcd斐蜀定理的迭代法和递归法,并解释了exgcd的概念和应用。exgcd是指对于不完全为0的非负整数a和b,gcd(a,b)表示a和b的最大公约数,必然存在整数对x和y,使得gcd(a,b)=ax+by。此外,本文还给出了相应的代码示例。 ... [详细]
  • EPICS Archiver Appliance存储waveform记录的尝试及资源需求分析
    本文介绍了EPICS Archiver Appliance存储waveform记录的尝试过程,并分析了其所需的资源容量。通过解决错误提示和调整内存大小,成功存储了波形数据。然后,讨论了储存环逐束团信号的意义,以及通过记录多圈的束团信号进行参数分析的可能性。波形数据的存储需求巨大,每天需要近250G,一年需要90T。然而,储存环逐束团信号具有重要意义,可以揭示出每个束团的纵向振荡频率和模式。 ... [详细]
  • VScode格式化文档换行或不换行的设置方法
    本文介绍了在VScode中设置格式化文档换行或不换行的方法,包括使用插件和修改settings.json文件的内容。详细步骤为:找到settings.json文件,将其中的代码替换为指定的代码。 ... [详细]
  • 本文介绍了在开发Android新闻App时,搭建本地服务器的步骤。通过使用XAMPP软件,可以一键式搭建起开发环境,包括Apache、MySQL、PHP、PERL。在本地服务器上新建数据库和表,并设置相应的属性。最后,给出了创建new表的SQL语句。这个教程适合初学者参考。 ... [详细]
  • Nginx使用(server参数配置)
    本文介绍了Nginx的使用,重点讲解了server参数配置,包括端口号、主机名、根目录等内容。同时,还介绍了Nginx的反向代理功能。 ... [详细]
  • 基于layUI的图片上传前预览功能的2种实现方式
    本文介绍了基于layUI的图片上传前预览功能的两种实现方式:一种是使用blob+FileReader,另一种是使用layUI自带的参数。通过选择文件后点击文件名,在页面中间弹窗内预览图片。其中,layUI自带的参数实现了图片预览功能。该功能依赖于layUI的上传模块,并使用了blob和FileReader来读取本地文件并获取图像的base64编码。点击文件名时会执行See()函数。摘要长度为169字。 ... [详细]
  • 电销机器人作为一种人工智能技术载体,可以帮助企业提升电销效率并节省人工成本。然而,电销机器人市场缺乏统一的市场准入标准,产品品质良莠不齐。创业者在代理或购买电销机器人时应注意谨防用录音冒充真人语音通话以及宣传技术与实际效果不符的情况。选择电销机器人时需要考察公司资质和产品品质,尤其要关注语音识别率。 ... [详细]
  • 这是原文链接:sendingformdata许多情况下,我们使用表单发送数据到服务器。服务器处理数据并返回响应给用户。这看起来很简单,但是 ... [详细]
  • 如何去除Win7快捷方式的箭头
    本文介绍了如何去除Win7快捷方式的箭头的方法,通过生成一个透明的ico图标并将其命名为Empty.ico,将图标复制到windows目录下,并导入注册表,即可去除箭头。这样做可以改善默认快捷方式的外观,提升桌面整洁度。 ... [详细]
author-avatar
只因为汰假_汰傻_615
这个家伙很懒,什么也没留下!
PHP1.CN | 中国最专业的PHP中文社区 | DevBox开发工具箱 | json解析格式化 |PHP资讯 | PHP教程 | 数据库技术 | 服务器技术 | 前端开发技术 | PHP框架 | 开发工具 | 在线工具
Copyright © 1998 - 2020 PHP1.CN. All Rights Reserved | 京公网安备 11010802041100号 | 京ICP备19059560号-4 | PHP1.CN 第一PHP社区 版权所有