2008年12月2日星期二

Write Yourself a Scheme in 48 Hours/First Steps

本章原文地址: http://en.wikibooks.org/wiki/Write_Yourself_a_Scheme_in_48_Hours/First_Steps

第一章的内容不多,准备知识而已。这一章的名字就叫“First Step”。起点也很朴实:开发环境的准备。

前面说过,这份例子是以最主流的Haskell编译器ghc为工具的。当然,ghc本身有一个还不错的shell环境,ghci。不过我们是要跟着写代码,做项目的,不能关机就扔,所以得要有个正经的源码编辑器。

作者推荐Emacs,如果你在Windows上,可以试试ntemacs。Emacs有个相当中规中矩的haskell-mode。里面的功能说实话我还没好好挖掘过,不过值得向大家推荐一下,默认的设置就算挺好用了。

如果你用不惯Emacs,其实VIM也不错,我本人是Emacs党,不过有回也试了试用VIM写Haskell代码,对于我这类初级用户,感觉没太 大差别。本书作者认为,hskell不是一种用记事本能搞定的语言。确实,虽然它只是纯文本,但是数学化的灵魂还在(还记得数学里那些鬼画符一样的表达式 吧),如果记事本,缩进格式足够把人搞疯的。不需要什么很伟大的IDE,有个靠谱的文本编辑器还是有必要的。

其实作者也有交待,你要是真用不惯这些 “非”主流编辑器,Eclipse有个 Fucntion Programming,联想到Eclipse连支持个XML都JJYY,这个Haskell插件竟然已经到了0.9.x,haskell这个圈子水真深啊……)。甚至还有个 http://www.haskell.org/visualhaskell/ ,看截图应该是VS6的,不过这种小众语言能支持这玩意儿已经很雷人了)。

另外,我相信一个另小众的编辑器:yi肯定是支持Haskell的。因为,它是Haskell写成的!这东西我没用过,不过谣言频道有人刷屏说这是一款向 Emacs 致敬的作品。从截图看真的有点像Emacs。

本章的示例代码不多,写个简单的入门例程:

module Main where
import System.Environment

main :: IO ()
main = do
args <- getArgs
putStrLn ("Hello, " ++ args !! 0)

如果你对Haskell还不熟……嗯……

module Main这一行用来声明一个模块,这东西比较像Python的模块概念(如果你没学过Python,这句话请无视)。虽然写个小脚本的话你不加这个也能跑,不过养成好习惯总是好的。

import 显而易见,就是用来导入外部模块的。这东西跟Python里用法差不多(如果你没学过Python,这句话请无视)。

4-7行很短,但是对于有学过其它编程语言的人会是一种颠覆性的打击,就算你上一门学的是Python也一样(如果你没学过Python,这句话请无视)。

第 4行是函数类型声明,它指明了main函数的类型是IO单子,这就是它的返回值。这在Haskell语言里是不同寻常的一种类型。我们先把这个事儿放一 边。之前如果你学过其它编程语言,那么我想你还记得,有些语言一定要有类型声明,比如C++或Java;有些语言根本没有类型声明,比如Python(如 果你没学过Python,这句话请无视)。

然而Haskell这个变态,是可以有类型声明,也可以没有的。如果你不写,它会自己猜,然后选匹配的类型里最常用的来编译(这一点不同于 Javascript的动态类型)。以我的经验,只要你程序写得正确没有bug,几乎不会出现猜不出来的。然而,自己明确指定类型,可运用到更精准和丰富 的数据类型。显然,从可读性上讲也是有好处的。当然,C#也有类似的匿名类型,Haskell学的C#也说不定。

如果你在知道Haskell是上世纪八十年代就有的语言以后还看不出上句话是一个冷笑话,还是不要学Haskell了……

顺便说一下,haskell对程序代码的排版规范要求非常严格,首先它的语法结构依赖缩进,这一点和Python一样(如果你没学过Python, 这句话请无视),子语句要缩进一级,这一点Haskell学的Python也说不定(参见上一段!)。其次,所有模块名都应该是PASCAL命名,所有函 数名都应该是驼峰命名,切记!

现在我们终于说到这个万恶的单子(Monad)了。作为一门纯函数语言,Haskell是相当数学化的,我个人觉得 Haskell根本就是一门代数语言。它的函数其实就是算法定义。所以要求必须是“确定的”。也就是说,函数内部除了返回值,不能影响到外部。函数本身输 入值固定的话,必须确定的返回固定的值。

但是这样的话,就没办法解决一些麻烦,比如程序运行过程中需要保存状态,程序本身需要输入输出(不然我们怎么看到结果呢?)。这些功能都是不符合确定性的。

所以,Monad就应运而生了。关于Monad的定义,Haskell有一套漂亮的表达。不过我现在不打算关注它。我们只要知道这个东西相当于可以传递和保存值的对象就可以了。现在我们的任务是,先学会用现有的Monad,比如本教程中提到的各种Monad。

Haskell的main函数相当于c的main函数,都是程序执行入口,所以没得说,这个肯定是一个Monad,事实上它必须是IO()。所以我们的程序代码也一定要匹配这个结果。

如果要返回一个Monad,有两种办法,一种是把返回值用return 函数封装(编译器会根据你的函数定义选择类型),术语称为lift,当然,被lift的值要匹配定义的Monad;另一种是组合多个Monad。

后者看起来麻烦,其实操作起来很简单。比如do操作,它后面可以带多行语句,每一行要么是一个action,要么是一个a<-action的 取值操作。这里的action,就是指Monand化的一个事物,可以理解为一个对象实例(那么一个Monad定义可看作是一个类定义)。

因为串化为一组Monad,do的子语句是按顺序执行的,do块相当于我们在普通的命令式语言中编写的代码片段。

想到这一点,或许你会初步体会到Haskell是一个多么与众不同的语言。

实际上do对每行代码有更细致的规定,它其实是一个语法糖,本原是一组Monad串行操作。如果原文你不能允分理解,没关系,后面我们还会大量运用Monad,我们会熟悉它的。

本章教程最后还讲解了运算符和例程的编译方法,这个没有太惊诧的东西,不多讨论了。

重点学习读取输入,打印字符到输出,基本编译指令。

以及,Haskell中字符串就是序列,它的联接用++,而不是单个加号。

没有评论: