第一章:入門

2018-02-24 15:49 更新

第一章:入門

Haskell編程環(huán)境

在本書的前面一些章節(jié)里,我們有時(shí)候會(huì)以限制性的、簡(jiǎn)單的形式來介紹一些概念。由于Haskell是一本比較深的語(yǔ)言,所以一次性介紹某個(gè)主題的所有特性會(huì)令人難以接受。當(dāng)基礎(chǔ)鞏固后,我們就會(huì)進(jìn)行更加深入的學(xué)習(xí)。

在Haskell語(yǔ)言的眾多實(shí)現(xiàn)中,有兩個(gè)被廣泛應(yīng)用,Hugs和GHC。其中Hugs是一個(gè)解析器,主要用于教學(xué)。而GHC(GlasgowHaskellCompiler)更加注重實(shí)踐,它編譯成本地代碼,支持并行執(zhí)行,并帶有更好的性能分析工具和調(diào)試工具。由于這些因素,在本書中我們將采用GHC。

GHC主要有三個(gè)部分組成。

  • ghc是生成快速本底代碼的優(yōu)化編譯器。
  • ghci是一個(gè)交互解析器和調(diào)試器。
  • runghc是一個(gè)以腳本形式(并不要首先編譯)運(yùn)行Haskell代碼的程序,

Note

我們?nèi)绾畏Q呼GHC的各個(gè)組件

當(dāng)我們討論整個(gè)GHC系統(tǒng)時(shí),我們稱之為GHC。而如果要引用到某個(gè)特定的命令,我們會(huì)直接用其名字標(biāo)識(shí),比如ghcghci,runghc。

在本書中,我們假定你在使用最新版6.8.2版本的GHC,這個(gè)版本是2007年發(fā)布的。大多數(shù)例子不要額外的修改也能在老的版本上運(yùn)行。然而,我們建議使用最新版本。如果你是Windows或者M(jìn)acOSX操作系統(tǒng),你可以使用預(yù)編譯的安裝包快速上手。你可以從GHC下載頁(yè)面 找到合適的二進(jìn)制包或者安裝包。

對(duì)于大多數(shù)的Linux版本,BSD提供版和其他Unix系列,你可以找到自定義的GHC二進(jìn)制包。由于這些包要基于特性的環(huán)境編譯,所以安裝和使用顯得更加容易。你可以在GHC的二進(jìn)制發(fā)布包頁(yè)面 找到相關(guān)下載。

我們?cè)赱附錄A]中提供了更多詳細(xì)的信息介紹如何在各個(gè)流行平臺(tái)上安裝GHC。

初識(shí)解釋器ghci

ghci程序是GHC的交互式解析器。它可以讓用戶輸入Haskell表達(dá)式并對(duì)其求值,瀏覽模塊以及調(diào)試代碼。如果你熟悉Python或是Ruby,那么ghci一定程度上和python,irb很像,這兩者分別是Python和Ruby的交互式解析器。

The ghci command has a narrow focus
We typically can not copy some code out of a haskell source file and paste it into ghci. This does not have a significant effect on debugging pieces of code, but it can initially be surprising if you are used to , say, the interactive Python interpreter.

在類Unix系統(tǒng)中,我們?cè)趕hell視窗下運(yùn)行ghci。而在Windows系統(tǒng)下,你可以通過開始菜單找到它。比如,如果你在WindowsXP下安裝了GHC,你應(yīng)該從”所有程序”,然后”GHC”下找到ghci。(參考附錄A章節(jié)Windows 里的截圖。)

當(dāng)我們運(yùn)行ghci時(shí),它會(huì)首先顯示一個(gè)初始banner,然后就顯示提示符Prelude>。下載例子展示的是Linux環(huán)境下的6.8.3版本。

$ ghci
GHCi, version 6.8.3: http://www.haskell.org/ghc/  :? for help
Loading package base ... linking ... done.
Prelude>

提示符Prelude標(biāo)識(shí)一個(gè)很常用的庫(kù)Prelude已經(jīng)被加載并可以使用。同樣的,當(dāng)加載了其他模塊或是源文件時(shí),它們也會(huì)在出現(xiàn)在提示符的位子。

Tip

獲取幫助信息

在ghci提示符輸入 :?,則會(huì)顯示詳細(xì)的幫助信息。

模塊Prelude有時(shí)候被稱為“標(biāo)準(zhǔn)序幕”(the standardprelude),因?yàn)樗膬?nèi)容是基于Haskell 98標(biāo)準(zhǔn)定義的。通常簡(jiǎn)稱它為“序幕”(theprelude)。

Note

關(guān)于ghci的提示符

提示符經(jīng)常是隨著模塊的加載而變化。因此經(jīng)常會(huì)變得很長(zhǎng)以至在單行中沒有太多可視區(qū)域用來輸入。

為了簡(jiǎn)單和一致起見,在本書中我們會(huì)用字符串 ‘ghci>' 來替代ghci的默認(rèn)提示符。

你可以用ghci的 :setprompt 來進(jìn)行修改。

Prelude> :set prompt "ghci>"
ghci>

prelude模塊中的類型,值和函數(shù)是默認(rèn)直接可用的,在使用之前我們不需要額外的操作。然而如果需要其他模塊中的一些定義,則需要使用ghci:module方法預(yù)先加載。

ghci> :module + Data.Ratio

現(xiàn)在我們就可以使用Data.Ratio模塊中的功能了。這個(gè)模塊提供了一些操作有理數(shù)的功能。
基本交互: 把ghci當(dāng)作一個(gè)計(jì)算器 除了能提供測(cè)試代碼片段的交互功能外,ghci也可以被當(dāng)作一個(gè)桌面計(jì)算器來使用。我們可以很容易的表示基本運(yùn)算,同時(shí)隨著對(duì)Haskell了解的深入,也可以表示更加復(fù)雜的運(yùn)算。即使是以如此簡(jiǎn)單的方式來使用這個(gè)解析器,也可以幫助我們了解更多關(guān)于Haskell是如何工作的。 基本算術(shù)運(yùn)算 我們可以馬上開始輸入一些表達(dá)式,看看ghci會(huì)怎么處理它們。基本的算術(shù)表達(dá)式類似于像C或是Python這樣的語(yǔ)言:用中綴表達(dá)式,即操作符在操作數(shù)之間。

ghci> 2 + 2
4
ghci> 31337 * 101
3165037
ghci> 7.0 / 2.0
3.5

用中綴表達(dá)式是為了書寫方便:我們同樣可以用前綴表達(dá)式,即操作符在操作數(shù)之前。在這種情況下,我們需要用括號(hào)將操作符括起來。

ghci> 2 + 2
4
ghci> (+) 2 2
4

上述的這些表達(dá)式暗示了一個(gè)概念,Haskell有整數(shù)和浮點(diǎn)數(shù)類型。整數(shù)的大小是隨意的。下面例子中的(^)表示了整數(shù)的乘方。

ghci> 313 ^ 15
27112218957718876716220410905036741257

算術(shù)奇事(quirk),負(fù)數(shù)的表示

在如何表示數(shù)字方面Haskell提供給我們一個(gè)特性:通常需要將負(fù)數(shù)寫在括號(hào)內(nèi)。當(dāng)我們要表示不是最簡(jiǎn)單的表達(dá)式時(shí),這個(gè)特性就開始發(fā)揮影響。

我們先開始表示簡(jiǎn)單的負(fù)數(shù)

ghci> -3
-3

上述例子中的-是一元表達(dá)式。換句話說,我們并不是寫了一個(gè)數(shù)字“-3”;而是一個(gè)數(shù)字“3”,然后作用于操作符-。-是Haskell中唯一的一元操作符,而且我們也不能將它和中綴運(yùn)算符一起使用。

ghci> 2 + -3

<interactive>:1:0:
    precedence parsing error
        cannot mix `(+)' [infixl 6] and prefix `-' [infixl 6] in the same infix expression

如果需要在一個(gè)中綴操作符附近使用一元操作符,則需要將一元操作符以及其操作數(shù)包含的括號(hào)內(nèi)。

ghci> 2 + (-3)
-1
ghci> 3 + (-(13 * 37))
-478

如此可以避免解析的不確定性。當(dāng)在Haskell應(yīng)用(apply)一個(gè)函數(shù)時(shí),我們先寫函數(shù)名,然后隨之其參數(shù),比如f3。如果我們不用括號(hào)括起一個(gè)負(fù)數(shù),就會(huì)有非常明顯的不同的方式理解f-3:它可以是“將函數(shù)f應(yīng)用(apply)與數(shù)字-3”,或者是“把變量f減去3”。

大多數(shù)情況下,我們可以省略表達(dá)式中的空格(“空”字符比如空格或制表符tab),Haskell也同樣能正確的解析。但并不是所有的情況。

ghci> 2*3
6

下面的例子和上面有問題的負(fù)數(shù)的例子很像,然而它的錯(cuò)誤信息并不一樣。

ghci> 2*-3

<interactive>:1:1: Not in scope: `*-'

這里Haskell把-理解成單個(gè)的操作符。Haskell允許用戶自定義新的操作符(這個(gè)主題我們隨后會(huì)講到),但是我們未曾定義過-。

ghci> 2*(-3)
-6

相比較其他的編程語(yǔ)言,這種對(duì)于負(fù)數(shù)不太一樣的行為可能會(huì)很些怪異,然后它是一種合理的折中方式。Haskell允許用戶在任何時(shí)候自定義新的操作符。這是一個(gè)并不深?yuàn)W的語(yǔ)言特性,我們會(huì)在以后的章節(jié)中看到許多用戶定義的操作符。語(yǔ)言的設(shè)計(jì)者們?yōu)榱藫碛羞@個(gè)表達(dá)式強(qiáng)項(xiàng)而接受了這個(gè)有一點(diǎn)累贅的負(fù)數(shù)表達(dá)語(yǔ)法。

布爾邏輯,運(yùn)算符以及值比較

Haskell中表示布爾邏輯的值有這么兩個(gè):True和False。名字中的大寫很重要。作用于布爾值得操作符類似于C語(yǔ)言的情況:(&&)表示“邏輯與”,(||)表示“邏輯或”。

ghci> True && False
False
ghci> False || True
True

有些編程語(yǔ)言中會(huì)定義數(shù)字0和False同義,但是在Haskell中并沒有這么定義,同樣的,也Haskell也沒有定義非0的值為True。

ghci> True && 1

<interactive>:1:8:
    No instance for (Num Bool)
      arising from the literal `1' at <interactive>:1:8
    Possible fix: add an instance declaration for (Num Bool)
    In the second argument of `(&&)', namely `1'
    In the expression: True && 1
    In the definition of `it': it = True && 1

我們?cè)僖淮蔚挠龅搅撕苡星罢靶缘腻e(cuò)誤。簡(jiǎn)單來說,錯(cuò)誤信息告訴我們布爾類型,Bool,不是數(shù)字類型,Num的一個(gè)成員。錯(cuò)誤信息有些長(zhǎng),這是因?yàn)間hci會(huì)定位出錯(cuò)的具體位置,并且給出了也許能解決問題的修改提示。 錯(cuò)誤信息詳細(xì)分析如下。 “No instance for (Num Bool)” 告訴我們ghci嘗試解析數(shù)字1為Bool類型但是失敗。 “arising from the literal 1'” 表示是由于使用了數(shù)字1而引發(fā)了問題。 “In the definition ofit'” 引用了一個(gè)ghci的快捷方式。我們會(huì)在后面提到。
Tip 遇到錯(cuò)誤信息不要膽怯 這里我們提到了很重要的一點(diǎn),而且在本書的前面一些章節(jié)中我們會(huì)重復(fù)提到。如果你碰到一些你從來沒遇到過的問題和錯(cuò)誤信息,別擔(dān)心(panic)。剛開始的時(shí)候,你所要的做的僅僅是找出足夠的信息來幫助解決問題。隨著你經(jīng)驗(yàn)的積累,你會(huì)發(fā)現(xiàn)錯(cuò)誤信息中的一部分其實(shí)很容易理解,并不會(huì)像剛開始時(shí)那么晦澀難懂。 各種錯(cuò)誤信息都有一個(gè)目的:通過提前的一些調(diào)試,幫助我們?cè)谡嬲\(yùn)行程序之前能書寫出正確的代碼。如果你曾使用過其它更加寬松(permissive)的語(yǔ)言,這種方式可能會(huì)有些震驚(shock).所以,拿出你的耐心來。 Haskell中大多數(shù)比較操作符和C語(yǔ)言以及受C語(yǔ)言影響的語(yǔ)言類似。

ghci> 1 == 1
True
ghci> 2 < 3
True
ghci> 4 >= 3.99
True

有一個(gè)操作符和C語(yǔ)言的相應(yīng)的不一樣,“不等于”。C語(yǔ)言中是用!=表示的,而Haskell是用/=表示的,它看上去很像數(shù)學(xué)中的≠。

另外,類C的語(yǔ)言中通常用!表示邏輯非的操作,而Haskell中用函數(shù)not。

ghci> not True
False

運(yùn)算符優(yōu)先級(jí)以及結(jié)合性

類似于代數(shù)或是使用中綴操作符的編程語(yǔ)言,Haskell也有操作符優(yōu)先級(jí)的概念。我們可以使用括號(hào)將部分表達(dá)顯示的組合在一起,同時(shí)操作符優(yōu)先級(jí)允許省略掉一些括號(hào)。比如乘法比加法優(yōu)先級(jí)高,因此以下兩個(gè)表達(dá)式效果是一樣的。

ghci> 1 + (4 * 4)
17
ghci> 1 + 4 * 4
17

Haskell給每個(gè)操作符一個(gè)數(shù)值型的優(yōu)先級(jí)值,從1表示最低優(yōu)先級(jí),到9表示最高優(yōu)先級(jí)。高優(yōu)先級(jí)的操作符先于低優(yōu)先級(jí)的操作符被應(yīng)用(apply)。在ghci中我們可以用命令:info來查看某個(gè)操作符的優(yōu)先級(jí)。

ghci> :info (+)
class (Eq a, Show a) => Num a where
  (+) :: a -> a -> a
  ...
    -- Defined in GHC.Num
infixl 6 +
ghci> :info (*)
class (Eq a, Show a) => Num a where
  ...
  (*) :: a -> a -> a
  ...
    -- Defined in GHC.Num
infixl 7 *

這里我們需要找的信息是“infixl 6+”,表示(+)的優(yōu)先級(jí)是6。(其他信息我們稍后介紹。)“infixl 7”表示()的優(yōu)先級(jí)為7。由于()比(+)優(yōu)先級(jí)高,所以我們看到為什么1+44和1+(44)值相同而不是(1+4)4。

Haskell也定義了操作符的結(jié)合性(associativity)。它決定了當(dāng)一個(gè)表達(dá)式中多次出現(xiàn)某個(gè)操作符時(shí)是否是從左到右求值。(+)和(*)都是左結(jié)合,在上述的ghci輸出結(jié)果中以infixl表示。一個(gè)右結(jié)合的操作符會(huì)以infixr表示。

ghci> :info (^)
(^) :: (Num a, Integral b) => a -> b -> a   -- Defined in GHC.Real
infixr 8 ^

優(yōu)先級(jí)和結(jié)合性規(guī)則的組合通常稱之為固定性(fixity)規(guī)則。

未定義的變量以及定義變量

Haskell的標(biāo)準(zhǔn)庫(kù)prelude定義了至少一個(gè)大家熟知的數(shù)學(xué)常量。

ghci> pi
3.141592653589793

然后我們很快就會(huì)發(fā)現(xiàn)它對(duì)數(shù)學(xué)常量的覆蓋并不是很廣泛。讓我們來看下Euler數(shù),e。

ghci> e

<interactive>:1:0: Not in scope: `e'

啊哈,看上去我們必須得自己定義。

不要擔(dān)心錯(cuò)誤信息
以上“not in the scope”的錯(cuò)誤信息看上去有點(diǎn)令人畏懼的。別擔(dān)心,它所要表達(dá)的只是沒有用e這個(gè)名字定義過變量。

使用ghci的let構(gòu)造器(contruct),我們可以定義一個(gè)臨時(shí)變量e。

ghci> let e = exp 1

這是指數(shù)函數(shù)exp的一個(gè)應(yīng)用,也是如何調(diào)用一個(gè)Haskell函數(shù)的第一個(gè)例子。像Python這些語(yǔ)言,函數(shù)的參數(shù)是位于括號(hào)內(nèi)的,但Haskell不要那樣。

既然e已經(jīng)定義好了,我們就可以在數(shù)學(xué)表達(dá)式中使用它。我們之前用到的乘方操作符(^)是對(duì)于整數(shù)的。如果要用浮點(diǎn)數(shù)作為指數(shù),則需要操作符(**)。

ghci> (e ** pi) - pi
19.99909997918947

Note

這是ghci的特殊語(yǔ)法

ghci 中 let 的語(yǔ)法和常規(guī)的“top level”的Haskell程序的使用不太一樣。我們會(huì)在章節(jié)“初識(shí)類型”里看到常規(guī)的語(yǔ)法形式。

處理優(yōu)先級(jí)以及結(jié)合性規(guī)則

有時(shí)候最好顯式地加入一些括號(hào),即使Haskell允許省略。它們會(huì)幫助將來的讀者,包括我們自己,更好的理解代碼的意圖。

更加重要的,基于操作符優(yōu)先級(jí)的復(fù)雜的表達(dá)式經(jīng)常引發(fā)bug。對(duì)于一個(gè)簡(jiǎn)單的、沒有括號(hào)的表達(dá)式,編譯器和人總是很容易的對(duì)其意圖產(chǎn)生不同的理解。

不需要去記住所有優(yōu)先級(jí)和結(jié)合性規(guī)則:在你不確定的時(shí)候,加括號(hào)是最簡(jiǎn)單的方法。

ghci里的命令行編輯

在大多數(shù)系統(tǒng)中,ghci有些命令行編輯的功能。如果你對(duì)命令行編輯還不熟悉,它將會(huì)幫你節(jié)省大量的時(shí)間?;静僮鲗?duì)于類Unix系統(tǒng)和Windows系統(tǒng)都很常規(guī)。按下向上方向鍵會(huì)顯示你輸入的上一條命令;重復(fù)輸入向上方向鍵則會(huì)找到更早的一些輸入??梢允褂?strong>向左和向右方向鍵在當(dāng)前行移動(dòng)。在類Unix系統(tǒng)中(很不幸,不是Windows),制表鍵(tab)可以完成輸入了一部分的標(biāo)示符。

[譯者注:]制表符的完成功能其實(shí)在Windows下也是可以的。

Tip

哪里可以找到更多信息

我們只是蜻蜓點(diǎn)水般的介紹了下命令行編輯功能。因?yàn)槊钚芯庉嬒到y(tǒng)可以讓你更加有效的工作,你可能會(huì)覺得進(jìn)一步的學(xué)習(xí)會(huì)有幫助。

在類Unix系統(tǒng)下,ghci使用功能強(qiáng)大并且可定制化的GNU readlinelibrary 。在Windows系統(tǒng)下,ghci的命令行編輯功能是由doskeycommand 提供的。

列表(Lists)

一個(gè)列表由方括號(hào)以及被逗號(hào)分隔的元素組成。

ghci> [1, 2, 3]
[1,2,3]

Note

逗號(hào)是分隔符,不是終結(jié)符

有些語(yǔ)言在表示列表時(shí)會(huì)在右中括號(hào)前多一個(gè)逗號(hào),但是Haskell沒有這樣做。如果多出一個(gè)逗號(hào)(比如 [1,2,] ),則會(huì)導(dǎo)致編譯錯(cuò)誤。

列表可以是任意長(zhǎng)度??樟斜肀硎境蒣]。

ghci> []
[]
ghci> ["foo", "bar", "baz", "quux", "fnord", "xyzzy"]
["foo","bar","baz","quux","fnord","xyzzy"]

列表里所有的元素必須是相同類型。下面例子我們違反了這個(gè)規(guī)則:列表中前面兩個(gè)是Bool類型,最后一個(gè)是字符類型。

ghci> [True, False, "testing"]

<interactive>:1:14:
    Couldn't match expected type `Bool' against inferred type `[Char]'
      Expected type: Bool
      Inferred type: [Char]
    In the expression: "testing"
    In the expression: [True, False, "testing"]

這次ghci的錯(cuò)誤信息也是同樣的很詳細(xì)。它告訴我們無法把字符串轉(zhuǎn)換為布爾類型,因此無法定義這個(gè)列表表達(dá)式的類型。

如果用列舉符號(hào)(enumerationnotation)來表示一系列元素,Haskell則會(huì)自動(dòng)填充內(nèi)容。

ghci> [1..10]
[1,2,3,4,5,6,7,8,9,10]

字符..在這里表示列舉(enumeration)。它只能用于那些可以被列舉的類型。因此對(duì)于字符類型來說這就沒意義了。比如對(duì)于["foo".."quux"],沒有任何意思,也沒有通用的方式來對(duì)其進(jìn)行列舉。

順便提一下,上面例子生成了一個(gè)閉區(qū)間,列表包含了兩個(gè)端點(diǎn)的元素。

當(dāng)使用列舉時(shí),我們可以通過最初兩個(gè)元素之間步調(diào)的大小,來指明后續(xù)元素如何生成。

ghci> [1.0,1.25..2.0]
[1.0,1.25,1.5,1.75,2.0]

ghci> [1,4..15]
[1,4,7,10,13]

ghci> [10,9..1]
[10,9,8,7,6,5,4,3,2,1]

上述的第二個(gè)例子中,終點(diǎn)元素并未包含的列表內(nèi),是由于它不屬于我們定義的系列元素。

我們可以省略列舉的終點(diǎn)(end point)。如果類型沒有自然的“上限”(upperbound),那么會(huì)生成無窮列表。比如,如果在ghci終端輸入[1..],那么就會(huì)輸出一個(gè)無窮的連續(xù)數(shù)列,因此你不得不強(qiáng)制關(guān)閉或是殺掉ghci進(jìn)程。在后面的章節(jié)章節(jié)中我們會(huì)看在Haskell中無窮數(shù)列經(jīng)常會(huì)用到。

Note

列舉浮點(diǎn)數(shù)時(shí)要注意的

下面的例子看上并不那么直觀

ghci> [1.0..1.8]
[1.0,2.0]

為了避免浮點(diǎn)數(shù)舍入的問題,Haskell就從 1.0 到 1.8+0.5 進(jìn)行了列舉。

對(duì)浮點(diǎn)數(shù)的列舉有時(shí)候會(huì)有點(diǎn)特別,如果你不得不用,要注意。浮點(diǎn)數(shù)在任何語(yǔ)言里都顯得有些怪異(quirky),Haskell也不例外。

列表的操作符

有兩個(gè)常見的用于列表的操作符。連接兩個(gè)列表時(shí)使用(++)。

ghci> [3,1,3] ++ [3,7]
[3,1,3,3,7]
ghci> [] ++ [False,True] ++ [True]
[False,True,True]

更加基礎(chǔ)的操作符是(:),用于增加一個(gè)元素到列表的頭部。它讀成“cons”(即“construct”的簡(jiǎn)稱)。

ghci> 1 : [2,3]
[1,2,3]
ghci> 1 : []
[1]

你可能會(huì)嘗試[1,2]:3給列表末尾增加一個(gè)元素,然而ghci會(huì)拒絕這樣的表達(dá)式并給出錯(cuò)誤信息,因?yàn)?:)的第一個(gè)參數(shù)必須是單個(gè)元素同時(shí)第二個(gè)必須是一個(gè)列表。

字符串和字符

如果你熟悉Perl或是C語(yǔ)言,你會(huì)發(fā)現(xiàn)Haskell里表示字符串的符號(hào)很熟悉。

雙引號(hào)所包含的就表示一個(gè)文本字符串。

ghci> "This is a string."
"This is a string."

像其他語(yǔ)言一樣,那些不顯而易見的字符(hard-to-see)需要“轉(zhuǎn)意”(escaping)。Haskell中需要轉(zhuǎn)意的字符以及轉(zhuǎn)意規(guī)則絕大大部分是和C語(yǔ)言中的情況一樣的。比如'\n'表示換行,'\t'表示制表符。完整的詳細(xì)列表可以參照附錄B:字符,字符串和轉(zhuǎn)意規(guī)則 。

ghci> putStrLn "Here's a newline -->\n<-- See?"
Here's a newline -->
<-- See?

函數(shù)putStrLn用于打印一個(gè)字符串。

Haskell區(qū)分單個(gè)字符和文本字符串。單個(gè)字符用單引號(hào)包含。

ghci> 'a'
'a'

事實(shí)上,文本字符串是單一字符的列表。下面例子展示了表示一個(gè)短字符串的痛苦方式,而ghci的顯示結(jié)果卻是我們很熟悉的形式。

ghci> let a = ['l', 'o', 't', 's', ' ', 'o', 'f', ' ', 'w', 'o', 'r', 'k']
ghci> a
"lots of work"
ghci> a == "lots of work"
True

""表示空字符串,它和[]同義。

ghci> "" == []
True

既然字符串就是單一字符的列表,那么我們就可以用列表的操作符來構(gòu)造一個(gè)新的字符串。

ghci> 'a':"bc"
"abc"
ghci> "foo" ++ "bar"
"foobar"

初識(shí)類型

盡管前面的內(nèi)容里提到了一些類型方面的事情,但直到目前為止,我們還沒有使用ghci進(jìn)行過任何類型方面的交互:即使不告訴ghci輸入是什么類型,它也會(huì)很高興地接受傳給它的輸入。

需要提醒的是,在Haskell里,所有類型名字都以大寫字母開頭,而所有變量名字都以小寫字母開頭。緊記這一點(diǎn),你就不會(huì)弄錯(cuò)類型和變量。

我們探索類型世界的第一步是修改ghci,讓它在返回表達(dá)式的求值結(jié)果時(shí),打印出這個(gè)結(jié)果的類型。使用 ghci 的:set命令可以做到這一點(diǎn):

Prelude> :set +t

Prelude> 'c'    -- 輸入表達(dá)式
'c'             -- 輸出值
it :: Char      -- 輸出值的類型

Prelude> "foo"
"foo"
it :: [Char]

注意打印信息中那個(gè)神秘的 it :這是一個(gè)有特殊用途的變量,ghci將最近一次求值所得的結(jié)果保存在這個(gè)變量里。(這不是Haskell語(yǔ)言的特性,只是 ghci 的一個(gè)輔助功能而已。)

Ghci 打印的類型信息可以分為幾個(gè)部分:

  • 它打印出 it
  • x::y 表示表達(dá)式 x 的類型為 y
  • 第二個(gè)表達(dá)式的值的類型為 [Char] 。(類型 String 是 [Char]的一個(gè)別名,它通常用于代替 [Char] 。)

以下是另一個(gè)我們已經(jīng)見過的類型:

Prelude> 7 ^ 80
40536215597144386832065866109016673800875222251012083746192454448001
it :: Integer

Haskell 的整數(shù)類型為 Integer 。 Integer類型值的長(zhǎng)度只受限于系統(tǒng)的內(nèi)存大小。

分?jǐn)?shù)和整數(shù)看上去不太相同,它使用 %操作符構(gòu)建,其中分子放在操作符左邊,而分母放在操作符右邊:

Prelude> :m +Data.Ratio
Prelude Data.Ratio> 11 % 29
11 % 29
it :: Ratio Integer

這里的 :m 是 :module 的縮寫,用于載入一個(gè)給定模塊。Ghci還提供了很多這類縮寫,方便使用者。

為了方便起見, ghci 給很多命令都提供了縮寫,這里的 :m 就是 :module的縮寫,它用于載入給定的模塊。

注意這個(gè)分?jǐn)?shù)的類型信息:在 :: 的右邊,有兩個(gè)單詞,分別是 Ratio 和Integer,可以將這個(gè)類型讀作“由整數(shù)構(gòu)成的分?jǐn)?shù)”。這說明,分?jǐn)?shù)的分子和分母必須都是整數(shù)類型,如果用一些別的類型值來構(gòu)建分?jǐn)?shù),就會(huì)造成出錯(cuò):

Prelude Data.Ratio> 3.14 % 8

<interactive>:8:1:
    Ambiguous type variable `a0' in the constraints:
        (Fractional a0)
            arising from the literal `3.14' at <interactive>:8:1-4
        (Integral a0) arising from a use of `%' at <interactive>:8:6
        (Num a0) arising from the literal `8' at <interactive>:8:8
    Probable fix: add a type signature that fixes these type variable(s)
    In the first argument of `(%)', namely `3.14'
    In the expression: 3.14 % 8
    In an equation for `it': it = 3.14 % 8

Prelude Data.Ratio> 1.2 % 3.4

<interactive>:9:1:
    Ambiguous type variable `a0' in the constraints:
        (Fractional a0)
            arising from the literal `1.2' at <interactive>:9:1-3
        (Integral a0) arising from a use of `%' at <interactive>:9:5
    Probable fix: add a type signature that fixes these type variable(s)
    In the first argument of `(%)', namely `1.2'
    In the expression: 1.2 % 3.4
    In an equation for `it': it = 1.2 % 3.4

盡管每次都打印出值的類型很方便,但這實(shí)際上有點(diǎn)小題大作了。因?yàn)樵谝话闱闆r下,表達(dá)式的類型并不難猜,或者我們并非對(duì)每個(gè)表達(dá)式的類型都感興趣。所以這里用 :unset 命令取消對(duì)類型信息的打?。?

Prelude Data.Ratio> :unset +t

Prelude Data.Ratio> 2
2

取而代之的是,如果現(xiàn)在我們對(duì)某個(gè)值或者表達(dá)式的類型不清楚,那么可以用:type 命令顯式地打印它的類型信息:

Prelude Data.Ratio> :type 'a'
'a' :: Char

Prelude Data.Ratio> "foo"
"foo"

Prelude Data.Ratio> :type it
it :: [Char]

注意 :type并不實(shí)際執(zhí)行傳給它的表達(dá)式,它只是對(duì)輸入進(jìn)行檢查,然后將輸入的類型信息打印出來。以下兩個(gè)例子顯示了其中的區(qū)別:

Prelude Data.Ratio> 3 + 2
5

Prelude Data.Ratio> :type it
it :: Integer

Prelude Data.Ratio> :type 3 + 2
3 + 2 :: Num a => a

在前兩個(gè)表達(dá)式中,我們先求值 3+2 ,再使用 :type 命令打印 it的類型,因?yàn)檫@時(shí) it 已經(jīng)是 3+2 的結(jié)果 5 ,所以 :type打印這個(gè)值的類型 it::Integer 。

另一方面,最后的表達(dá)式中,我們直接將 3+2 傳給 :type ,而 :type并不對(duì)輸入進(jìn)行求值,因此它返回表達(dá)式的類型 3+2::Numa=>a 。

第六章會(huì)介紹更多類型簽名的相關(guān)信息。

行計(jì)數(shù)程序

以下是一個(gè)用 Haskell寫的行計(jì)數(shù)程序。如果暫時(shí)看不太懂源碼也沒關(guān)系,先照著代碼寫寫程序,熱熱身就行了。

使用編輯器,輸入以下內(nèi)容,并將它保存為 WC.hs :

-- file: ch01/WC.hs
-- lines beginning with "--" are comments.

main = interact wordCount
    where wordCount input = show (length (lines input)) ++ "\n"

再創(chuàng)建一個(gè) quux.txt ,包含以下內(nèi)容:

Teignmouth, England
Paris, France
Ulm, Germany
Auxerre, France
Brunswick, Germany
Beaumont-en-Auge, France
Ryazan, Russia

然后,在 shell 執(zhí)行以下代碼:

$ runghc WC < quux.txt
7

恭喜你!你剛完成了一個(gè)非常有用的行計(jì)數(shù)程序(盡管它非常簡(jiǎn)單)。后面的章節(jié)會(huì)繼續(xù)介紹更多有用的知識(shí),幫助你(讀者)寫出屬于自己的程序。

[譯注:可能會(huì)讓人有點(diǎn)迷惑,這個(gè)程序明明是一個(gè)行計(jì)數(shù)(line count)程序,為什么卻命名為 WC(word count)呢?實(shí)際上,在接下來的練習(xí)小節(jié)中,讀者需要對(duì)這個(gè)程序進(jìn)行修改,將它的功能從行計(jì)數(shù)改為單詞計(jì)數(shù),因此這里程序被命名為 WC.hs 。]

練習(xí)

  1. ghci里嘗試下以下的這些表達(dá)式看看它們的類型是什么?
  • 5+8
  • 3*5+8
  • 2+4
  • (+)24
  • sqrt16
  • succ6
  • succ7
  • pred9
  • pred8
  • sin(pi/2)
  • truncatepi
  • round3.5
  • round3.4
  • floor3.7
  • ceiling3.3
  1. ghci里輸入:?以或許幫助信息。定義一個(gè)變量,比如letx=1,然后輸入:showbindings.你看到了什么?
  2. 函數(shù)words計(jì)算一個(gè)字符串中的單詞個(gè)數(shù)。修改例子WC.hs,使得可以計(jì)算一個(gè)文件中的單詞個(gè)數(shù)。
  3. 再次修改WC.hs,可以輸出一個(gè)文件的字符個(gè)數(shù)。
以上內(nèi)容是否對(duì)您有幫助:
在線筆記
App下載
App下載

掃描二維碼

下載編程獅App

公眾號(hào)
微信公眾號(hào)

編程獅公眾號(hào)