分类目录归档:程序相关

C,Python,环境配置等

Scheme学习笔记2: 输入和输出

本文包括以下内容:

作为一门语言,它需要提供与外设交互的方式,在各种语言中都提供了输入输出,Scheme语言也不例外。Scheme语言的输入输出功能,是在C基础上的一种封装。

read和write

输入输出中我们常用是读和写,对应读与写,在Scheme中有read和write。
read是读操作,它将 Scheme 对象的外部表示转换为对象本身。
read的标准格式为:(read port)
其中port参数可以省略,此时它使用的默认是current-input-port的返回值。

write是写操作,它的标准格式为:(read obj port)
write过程的作用是向给定端口port输出obj的内容。其中输出内容的反斜线、双引号等会被反斜线转义。write的参数中port参数可以省略,默认情况下使用current-output-port的返回值。

这里的默认值我们可以对应到标准输入和输出。从ZOJ的第一题我们看下read的使用。
如下代码:

ZOJ第一题(zoj1001):

(define (main)
 
(let ((a (read)) (b (read)))
 
(if (not(eof-object? a))
(begin
(display (+ a b))
(newline)
(main)
)
)
)
 
)
 
(main)

题目很简单,就是输入两个数,输出和。这里的难点是对于输入结束的判断,在C语言中scanf函数有一个EOF的判断,而在Scheme语言中,我们通过判断输入的输入的值是否为eof来判断,其对应的判断过程为eof-object?(嗯,问号也是调用过程的组成部分,个人非常喜欢这种表达方式),在判断不为结束时继续递归调用,实现C语言中的while循环。

字符操作

对于字符的读写操作,我们常用的三个过程如下:

read-char
标准格式: (read-char port)
读取一个字符,并将port指针指向下一个字符。
read-char的参数中port可以省略,默认情况下使用current-input-port的返回值。

write-char
标准格式: (write-char port)
write-char过程的作用是向给定端口port输出的内容。
write-char的参数中port可以省略,默认情况下使用current-output-port的返回值。

peek-char
标准格式: (peek-char port)
获取一个字符,但并不将port指针指向下一个字符
peek-char的参数中port可以省略,默认情况下使用current-input-port的返回值。

以一个示例说明这三个过程的调用,示例实现从指定文件中按字符读取内容,并放到一个列表中。

(define input_port (open-input-file "temp"))
 
(define read-char-with-file (lambda (port)
(if (eof-object? (peek-char port))
'()
(cons (read-char port) (read-char-with-file port))
)
)
)
 
(if (input-port? input_port)
(begin
(display (read-char-with-file input_port))
(close-input-port input_port)
)
 
(begin
(display "read file form temp failed")
(newline)
)
)

这段代码写得有些复杂,我们可以通过let关键字和call-with-input-file过程来简化这段代码,如下:

(define output-chars
(call-with-input-file "temp"
(lambda (p)
(let f ((x (read-char p)))
(if (eof-object? x)
'()
(cons x (f (read-char p)))
) 
)
)
)
)
 
(display output-chars)
(newline)

call-with-input-file的标准调用格式为:

procedure: (call-with-input-file path procedure)

call-with-input-file过程给指定的文件创建一个port,并将这个port传递给第二个参数指定的过程procedure,当procedure执行完成时,call-with-input-file过程将关闭打开的输入port并返回procedure返回的值。

文件操作

上面的示例已经提了文件操作,关于文件操作,除了针对输入和输出的打开port和关闭port,判断port是否存在的过程外,我们常用还有判断文件存在,删除文件,Scheme提供的这两个过程来与文件系统交互:file-exists?和delete-file
file-exists?的作用是判断文件是否存在,其调用格式如下:
(file-exists? path)
如果文件存在,返回#t,否则返回#f

delete-file的作用是删除一个文件,其调用格式如下:
(delete-file path)

path是字符串类型,如果所给的文件不存在,则会显示错误:No such file or directory

在使用delete-file之前可以调用file-exists?来判断文件是否存在。

除了上面介绍的一些简单输入输出,Scheme还提供了更强大和灵活的输入输出机制,如对编码的处理,对二进制的处理,对字符串的操作等,具体可以见官方文档或《The Scheme Programming Language, 4th Edition》

Scheme学习笔记一

Scheme学习笔记

本文包括如下内容:

  • 开发环境配置
  • hello world
  • 变量、过程
  • 标准输入
  • if语句
  • 文件写入

环境

“工欲善其事 必先利其器”。

学习之前我们需要先将Scheme的运行环境搭建起来,环境分Win和Linux,在Win下,直接到 http://www.gnu.org/software/mit-scheme/ 下载,安装。

在Ubuntu下,使用apt-get安装(强制建议使用*inux环境),Scheme的解释器有多种选择,这里我们选择mzscheme和guile,二者都可。

; mzscheme安装
sudo apt-get install  mzscheme
 
;guile 解释器安装
sudo apt-get install guile-1.8

Hello World

创建hello.scm文件,在编辑器(vi/emacs)中输入如下内容:

;hello.scm the first program
(begin
(display "hello world!")
(newline))

在命令行下,guile hello.scm 或 mzscheme -r hello.scm

如果你直接使用guile或mzscheme命令进入其命令行模式。则可以通过(load “hello.scm”)加载执行,注意,这里的括号是必须的。

解释下这段代码:

在分号(;)之后一直到行末之间就是注释。如hello.scm的开头部分。标准的Scheme语言定义中没有多行注释,不过在它部分实现中还是存在的。比如在Guile中就有多行注释,以符号组合“#!”开始,以相反的另一符号组合“!#”结束,其中内容为注释。如果加了这样的注释,这个程序就只能用guile来运行,如果使用mzscheme命令来运行则会报错。如果考虑代码的通用性,不建议使用此类注释。

display是输出过程,将内容输出到屏幕, newline的作用是换行显示。

变量,过程

在scheme中变量是通过define关键字定义,通过set!改变,并且scheme的变量是固定的类型,这有些类似于现在的python, php等动态语言,其在运行过程中是可变的,或者说这些动态语言是从函数式编程语言中学习了这些特性。 scheme的过程定义也是通过define关键字来实现的,只是其格式与变量有一些不同。

结合上面的hello world示例,我们看一个示例:

 
;var.scm
 
    ; 定义变量x
    (define x 10)
 
    ;定义过程cout
    (define (cout x)
    (display x)
    (newline)
    )
 
    (begin
    (cout x)
 
    ; 改变变量x的值, 这里没有改变类型
    (set! x 20)
    (cout x)
 
    ; 改变变量x的值,这里将之前的整型换成了字符串
    (set! x "string test")
    (cout x)
 
    )

定义过程除了上面示例中的方式外,还可以使用lambda来定义过程,其格式如下:

(define 过程名 ( lambda (参数 ...) (操作过程 ...)))

而我们的示例中的格式为:

(define (过程名 参数 ...) (操作过程 ...))

这两种方式都可以使用,如果你是需要使用匿名函数,使用lambda方案,去掉定义性内容,如下:

 ( lambda (参数 ...) (操作过程 ...))

标准输入

从标准输入读取字符串,这里只用到了read过程,如下示例:

 
; 标准输入
    (begin
 
    (define str (read))
 
    (display str)
    (newline)
    )

通过定义 str为从标准输入获取内容,即调用 read 获取,以换行或空格结束输入程序通过display过程打印输入的内容,调用newline换行输出。

if 语句

和我们常见的if语句结构一样,Scheme语言的if结构有两种格式,

  • 一种格式为:(if 测试 过程1 过程2),对比PHP相当于if else,即测试条件成立则执行过程1,否则执行过程2。
  • 另一种格式为:(if 测试 过程) ,对比PHP,相当于没有else语句的方式,即测试条件成立则执行过程,没有else情况发生。

此时,可能会想在PHP中还有elseif语句呢,那么在Scheme中是怎样的?在Scheme中没有else语句也没有相关结构,它是以另一外if语句实现,如果我们在PHP中,则其结构类似于:

   if () {
 
    }else {
      if () {   //  if语句的嵌套,在Scheme只能此种方案,没有简化后的elseif
 
      }
    }

例如下面代码:

(define a (read))
    (define b (read))
 
    ; 输出结果,包含从数字到字符串的转化
    (define (display_rs x)
    (display (string-append (number->string a) x (number->string b)))
    )
 
    (if (< a b) 
    (display_rs "<")
    (if (= a b)
    (display_rs "=")
    (display_rs ">")
    )
    )
 
    (newline)

示例程序从标准输入接收两个数字,a和b。对比两个数字,显示最终谁大谁小。 其中,string-append是字符串连接过程,number->string的作用是数字转化为字符串。 display_rs是一个自定义的过程,显示比较的结果用,其传入的值为比较后的结果。

文件写入:

 
;file.scm 文件操作
 
    ; 文件写入操作
    (define port (open-output-file "temp"))
 
    (if (output-port? port)
 
    (begin
    (write "helloworld" port)
    (close-output-port port)
    (display "yes")
    (newline)
    )
 
    (begin
    (display "write file temp failed.")
    (newline)
    )
 
    )

《Scheme 程序语言介绍之一》这篇文章的开头处有一段话,虽有些玩笑之言,却值得沉思:

在网上有这样一句有趣的评论:计算机科学的大部分,就是在重复发现很久以前别人就早已 发现过的东西。
当然,这是一句玩笑。不过我们可以给这句玩笑接个下巴:对于程序语言中 的每一个重要概念,
你都可以先在 Lisp 当中发明一次,再在 C++ 里面发明一次,再在 Java 里面发明一次,
再在 Python 里面发明一次,再在 Perl 里面发明一次,再在 Ruby 里面发明一次,
当然,最后还要在 C# 里面再发明一次。

在学习的过程中,参考了如下书籍和链接:

  • 《The Scheme Programming Language》
  • 《Structure and Interpretation of Computer Programs,Second Edition》
  • http://zh.wikipedia.org/zh/Scheme
  • http://www.ibm.com/developerworks/cn/linux/l-scheme/part2/
  • http://www.ibm.com/developerworks/cn/linux/l-schm/index1.html
  • http://www.ibm.com/developerworks/cn/linux/l-schm/index2.html

与代码的相处之道

与代码的相处之道 — 读《编程人生》一二章有感

最近在阅读《编程人生》,看了作者对Jamie Zawinski(Lisp黑客、XEmacs开发者、Netscape浏览器和Mozilla核心开发者)和Brad Fitzpatrick(80后程序员、LiveJournal和memcached开发者,Google员工)的采访,除了感慨作者采访的准备充分外,对于牛人的一些观点有一些共鸣,也有一些观点不太认同,尽信书不如无书,对于牛人也是如此吧。
一个程序员,大多数时间都在写代码,调试代码,阅读代码、评论他人的代码……林林总总,都是与代码相处。那如何与代码相处呢?首先我们需要认知到代码是什么,代码是一种语言,一种我们与计算机沟通的语言。我们需要通过代码告诉那个有点傻傻的计算机需要做点什么。

对于一些新增加的内容,我们可以从如下的方面与代码相处。

  • 第一,了解写这些代码是为了什么,或者说你的需求是什么,列出所有的功能点,估算你实现这些功能需要多长的时间,在估算的过程中,你不能将自己的工作时间完全算在开发中,大概50%差不多,毕竟我们不会一整天的写,还会有思考,发呆,梦游,开各种网页,看各种新闻,被各种im打断……
  • 第二,为你的新的功能搭出基本的架子,比如写好空类或空的函数,后面的工作就是填充这些空的地方了。当然在写这些空类或空函数的过程中,把注释写清楚,这是一个理顺自己代码结构和业务逻辑的过程。
  • 第三,这一步当然就是填充之前留空的内容了,在整个过程中你可能会写入一些调试的代码,没关系,先放着,因为在这个过程中你可能会再次需要这些调试的代码。
  • 第四,验证需求,重构代码。每个设计方案在开始的时候都是完美的,包括就在刚才你的代码结构设计。只是在细化需求时,可能部分细节没有考虑到,此时你就需要重构刚才的设计和代码,以适应这些变化。
  • 第五,打完收工,这是一个收尾的工作。此时,我们需要再次阅读你刚才写好的代码,清除这个过程中出现的调试代码,确认整个过程是否已经全部完成了所需要的功能点。
  • 第六,提交

如果你不是重新开始写一个功能,而是在别人(或你自己之前)的代码上修改并增加新的功能,此时当如何相处呢?

  • 第一,了解过去,分析影响范围,列出checklist。一段旧的代码,你最好先了解他的过去,看他与哪些其它模块有耦合,或者有哪些内容依赖于他,如果修改了这些内容,对其他模块是否有影响,如果要修改的是对外的接口,是否需要适配这些接口?
  • 第二,修改代码。此时你可能会重构之前的代码,那么在重构的过程中需要把握重构的度,不要轻易的将重构变成重写。毕竟之前的一些细节可能是你没有考虑到的。如果修改的代码中有对外的接口,此时可能需要保留这些接口,或者修改所有的调用这个接口的地方,这个就要权衡两者的机会成本了 。
  • 第三,根据checklist验证所修改的内容是否正确,验证是否影响了其它相关的内容
  • 第四,提交

如果是不是重新开始写一个功能,而是由于bug或其它原因需要调试代码呢?此时当如何相处?

  • 第一,了解它。我们需要先通读代码,审阅整个代码的结构,确认在整体的方向上没有问题。
  • 第二,在关键点打印信息,当然,你也可以使用调试工具与之交互,但是打印语句是一种绿化无污染的调试方式,不依赖于外部的环境,不过你需要通过第一步,先对代码有一定的了解才行。
  • 第三,确认问题,修改。此时可能需重复上面的修改流程
  • 第四,提交

前面三个都是通过代码告诉计算机怎么做,在某些时候我们也需要计算机告诉我们他做了什么。因此,在这里我们也需要通过代码告诉计算机如何将信息反馈给我们。一般来说,需要告诉他如何将整个代码执行过程中发生了什么告诉我们,比如日志,比如执行过程中的信息打印。

如果你是想优化一些代码,此时当如何相处呢?

  • 第一,确认需要优化的内容。可能你需要优化时间,也可能是需要优化空间,确认优化的内容。嗯,Knuth说过:过早优化是万恶之源。
  • 第二,找出瓶颈。优化并不是优化所有的地方,而是要找到优化的关键点,比如循环调用许多次的函数,或者花费时间或空间较多的地方等等。
  • 第三,寻找优化方案。可能你需要的仅仅是调换一下代码的执行顺序,或者释放执行过程中的某些变量所占的内存,当然也有可能需要优化整个数据结构,或者换台机器也是一个不错的主意。
  • 第四,验证优化结果。
  • 第五,提交

优化是一个持续的过程,在写代码的过程中能够随手优化的就优化吧,比如局部变量的使用等。

代码是我们与计算机沟通的语言,也是我们与其它程序员沟通的语言,因此除了让计算机了解代码外,我们也需要为其它程序员(或一段时间后的自己)了解这些代码做点什么,除了团队内部构建良好的代码规范,统一代码风格这些老生常谈(老生常谈其实挺好)外,书中的牛人提出关于注释的观点也非常认同:关于注释我们得写点不一目了然的东西。至少不要出现类似于循环结束,某某值加1的注释,一般来说注释应该是写点与业务相关的东西,至少在第一眼看代码无法看出来的东西。虽然现在敏捷提倡“代码即文档”,不要注释,提高代码的可读性,但是一些注释还是必要的。