Write Yourself a Scheme in 48 Hours(2)


2. 第一步

首先,你需要安装 GHC 。在 Linux 环境,它常常被预安装了或者能够通过 apt-get 或者 yum 命令获得。它也可以从 http://www.haskell.org/ghc/ 下载。二进制包大概是最容易的,除非你真的知道你在做什么。 GHC 应该像其他的软件包一样下载和安装。这个教程在 Linux 下面完成,但是如果你知道如何使用 DOS 命令行所有的东西应该能够在 Windows 环境下工作。

有一个非常棒的 Emacs mode 提供给 UNIX (或者 Windows Emacs) 用户,包括语法高亮和自动缩进。 Windows 用户能够使用记事本或者其他文本编辑器; Haskell 语法对记事本相当友好,尽管你需要对缩进小心处理。

 

现在,是时候写你的第一个 Haskell 程序。这个程序将通过命令行读入一个名字然后输出一个问候。建立一个文件一 ".hs” 结尾并输入下列代码:

module Main where
import System.Environment

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

让我们看看这段代码。最开始的两行指定了我们将创造一个以 Main 命名的模块,这个模块将引入 (import) System 模块。每一个 Haskell 程序从一个 Main 模块的 main 函数开始。那个模块可以导入其它模块,但是必须把它提供给编译器生成一个可执行文件。 Haskell 是区分大小写的:模块的名字永远是大写开头的,生命永远是非大写开头的。

 

main :: IO () 这一行是一个类型声明: "main” 函数有 "IO ()” 类型。类型声明在 Haskell 里面是可选的:编译器能够自动的识别他们,当你的声明和编译器自动识别发生冲突的时候它会报错。在这个教程里,为了更清晰,所有的类型声明我都显式声明了。当你在家里跟随这个教程的时候,你可能想要忽略这些类型声明,因为在我们编写这个程序的时候不需要改动它们。

 

IO 类型是 monad 的一个实例, monad 是一个吓人的名词不过含义却很简单。简单说, monad 是一个声明“有一些附加信息,大多数函数都不要对这些附加信息担心”的方法。在这个例子里,“附加信息”是这个函数执行了 IO 的情况,而它的基本值是无,用 "()” 表示。 Monadic 值常被称作 "actions” ,因为这是最容易的方法来思考 IO monad 是一系列的动作,而这些动作可能会影响外界世界。

 

Haskell 是一个声明式语言:除了告诉计算机一系列需要执行的指令,你还告诉它如何执行这些函数的定义。这些定义将各种动作和函数组合在一起。编译器识别出把所有定义放在一起的执行路径。( bad

 

要写一个这样的定义,你需要建立一个等式。等式的左边定义一个名字,可能会有一个或者更多的会与变量帮定的 patterns (后面解释 ) 。等式的右边定义 (TODO) <!-- @page { margin: 0.79in } P { margin-bottom: 0.08in } A:link { so-language: zxx } -->

了一些其它告诉计算机当碰到这些名字应该干什么的定义。这些等式表现得就像一般的代数等式:你总是可以在程序里用右边的部分替代左式,这种替换不会改变计算值。这种行为被称作 "referential transparency" ,这种性质使得 Haskell 代码比其它的语言容易懂得多。

那我们又要如何定义 main 函数呢?我们知道必须有一个 IO () 动作,而且我们想要它读入命令行参数,然后答应一些输出。这里有两种方法创建一个 IO 动作:

  1. return 函数提升 (lift) 一个普通值进入 IO monad

  2. 把两个 IO 动作连接起来。

因为我们要做两件事情,我们将使用第二种方法。内建动作 getArgs 读入所有命令行参数并把它们存入一个字符串列表。内建函数 putStrLn 将一个字符串输出到终端。

我们使用 do-block 连接它们。 do-block 包括很多行,所有的行按照第一个非空白字符在 do 后面排列。每一行能有下面两种形式之一:

  1. name <- action

  2. action

第一种形式将 action 的结果和 name 绑定。例如,如果有一个动作的类型是 IO [String]( 一个 IO 动作会返回一个字符串列表,就像 getArgs) ,然后 "name” 就会和这个返回的字符串列表绑定。爹人中形式会执行这个动作,把上面的行用 >> 操作符连结在一起。这个操作符在不同的 monad 里面有不同的语义:如果在 IO monad 中,他会连续执行所有的动作,执行无论什么会产生外部副作用的操作。因为这个连接符的语义依赖你使用的 monad ,所以你不能在同一个 do-block 里把不同 monad 类型的操作连在一起。

当然,这些动作可能自己就是函数的结果或者复杂的表达式。在这个例子里,我们首先取出参数列表中的 0 元素 (args !! 0) ,把他连接到字符串 "Hello, “ 的后面 (“Hello, “ ++) ,最后把结果传给 putStrLn 。字符串在 Haskell 是一串字符,所以在字符串上你可以使用任何列表函数和操作符。一个标准操作符和它们的优先权如下表:

(table omited)

编译和运行这个程序,你可以这么做:

 

debian:/home/jdtang/haskell_tutorial/code# ghc -o hello_you listing2.hs
debian:/home/jdtang/haskell_tutorial/code# ./hello_you Jonathan
Hello, Jonathan

  -o 选项指定了你想创造的可执行程序的名字,然后你指定 Haskell 源程序名字。

 

习题:

1.修改程序,让它从命令行读入两个参数,然后输入一个包括这两个参数的信息。

2.修改程序,让它对输入的两个参数执行一个简单的算术操作然后把结果输出。你可以使用 read 来转换字符串成数字,和 show 转换一个数字回字符串。再用不同的操作符试试。

3.getLine 是一个 IO 动作,它能从终端读入一行然后把结果作为字符串返回。修改程序让它提示需要一个名字,然后读入名字,最后输出它。而不是从命令行读入。

 

 

 

你可能感兴趣的:(linux,Debian,Scheme,haskell,emacs)