Archive for November, 2008

[Haskell筆記]list

Sunday, November 30th, 2008

函數是構建Haskell程序的兩大基本部件之一,另一個則是list(列表)。

list的表示方法很簡單,首先列舉list的各成員,然後以“,”分隔各成員,最後用“[”和“]”標記list的開頭和結尾。比如,[1,2,3,4]。

為了便於以後的稱說,我們通常給list“起個名字”(指定一個符號代表它),例如:

Prelude> let numbers = [1,2,3,4]
Prelude> let truths  = [True, False, False]
Prelude> let strings = ["here", "are", "some", "strings"]

特別要當心的是,list中的成員必須擁有相同的類型。在上面的三個例子中,numberslist的成員都是Integer(整數),truthslist的成員都是Bool(布爾值),stringslist的成員都是String(字符串)(字符串用""括起來)。不同類型的成員無法在一個list中共存。比如,如果輸入:

Prelude> let mixed = [1,True]

GHCi就會報錯:

<interactive>:1:13:
    No instance for (Num Bool)
      arising from the literal `1' at <interactive>:1:13
    Possible fix: add an instance declaration for (Num Bool)
    In the expression: 1
    In the expression: [1, True]
    In the definition of `mixed': mixed = [1, True]

這是因為1的類型是Integer,而True的類型是Bool,兩者類型不同。

練習

6.1 以下哪些list可以成立,哪些不可以,為什麽?

    1. [12, 80]
    2. [42, "life, universe and everything else"]
    3. ["beer", "sandwiches"]
    4. [5, 4.5]
    5. ["45", "True"]
    6. [pi, 56]

答案

5.1 25

Prelude> areaSquare 5
25

5.2

Prelude> let areaCircle r = pi * r ^ 2
Prelude> let volCylinder r h = areaCircle r * h

5.3

Prelude> let areaRect l w = l * w
Prelude> let areaSquare s = areaRect s s
Prelude> let areaCircle r = pi * r ^ 2
Prelude> let diffOfCnS r = areaCircle r - areaSquare r

[Haskell筆記]函數定義中使用函數

Thursday, November 27th, 2008

定義函數時,可以使用其它已經定義好的函數。比如,求正方形的面積。正方形是矩形的一個特例,所以定義求正方形面積的函數時,可以利用求矩形面積的函數。

Prelude> let areaRect l w = l * w
Prelude> let areaSquare s = areaRect s s

練習

5.1 利用areaSquare求邊長為5的正方形的面積。
5.2 寫一個求圓柱體體積的函數(利用求圓面積的函數)
5.3 寫一個函數解決以下問題:給定圓的半徑,且已知有一正方形的邊長和該半徑相等,求該圓與該正方形面積之差。


答案

4.1 areaTriangle b h = (b * h) / 2

4.2 areaCube l w h = l * w * h

[Haskell筆記]多元函數

Tuesday, November 18th, 2008

上次我們討論了求圓面積的函數,即

f r = pi * r ^ 2

這裏f代表function(函數),表達的信息量很有限。如果我們的程序裏有很多這樣的函數,就很不方便。你得讀完整行纔知道這是一個求圓的函數。為了增加可讀性(或者說方便偷懶,不用讀完函數的整個定義就知道函數是幹啥的),寫成類似以下的形式好一些:

areaCycle r = pi * r ^ 2

好了,求了圓的面積,求了圓的周長(練習3.1),下面我們來求矩形的面積areaRect。和圓不一樣,長方形的面積取決於長和寬。那該怎麽寫呢?很簡單,以空格隔開,依次列出就可以了。

Prelude> let areaRect l w = l * w

那麽,長為5,寬為10的矩形的面積為:

Prelude> areaRect 5 10
50

練習

4.1 寫一個求三角形面積的函數。
4.2 寫一個求長方體體積的函數。


答案:

3.1

g r = 2 * pi * r

拼音有的时候会起干扰作用

Tuesday, November 18th, 2008

例子很多,只举一个,xiong,看上去是齐齿呼,其实是撮口呼。

山寨硬盘盒

Tuesday, November 18th, 2008

送了一把螺丝刀。问题在于这把螺丝刀和硬盘盒上的螺丝不匹配。

[Haskell筆記]函數

Monday, November 17th, 2008

引入了符號這個概念後,計算圓的面積輕松多了。

Prelude> pi * (5 ^ 2)
78.53981633974483
Prelude> pi * (25 ^ 2)
1963.4954084936207

可不可以再偷點懶呢?當然可以,每次計算圓面積的時候,僅僅換了半徑的值而已,每次都重復輸入圓面積公式,太麻煩了。這就要用到函數了。

在圓面積公式

s = pi*r^2

中,每次改變的只是r。那麽,可以認為s是r的一個函數,即

s=f(r)=pi*r^2

上述函數,在Haskell中這樣表達:

f r = pi * r ^ 2

和數學上的表達方式很相似,只是用空格代替了環繞自變量(在Haskell中稱為parameter)的括號而已。

值得注意的是,y=f(x)這樣的表達在Haskell中不成立。比如,如果你嘗試用s來代表求圓面積的函數:

Prelude>let s = f r = pi * r ^ 2

或者,

Prelude>let s = f r

GHCi都會報錯。在Haskell中,符號可以代表具體的值,也可以代表含有其它符號的表達式,比如:

Prelude>let doublePi = 2 * pi

後者其實是間接的代表具體的值,比如,上式也可以直接寫成:

Prelude>let doublePi = 6.283185307179586

但是,符號代表的值必須是不變的!而且,符號不可以用來代表一個函數。

好了,現在來嘗試一下我們的函數吧。

Prelude> let f r = pi * r ^ 2
Prelude> f 5
78.53981633974483
Prelude> f 25
1963.4954084936207

是不是很方便?

練習3.1 寫一個計算圓周長的函數。

bash中正則的反向引用

Monday, November 17th, 2008

例子:輸入abcd123,輸出abcd

sed是這樣的:

s/\([a-z]*\).*/\1/

perl中是這樣的:

s/([a-z]*).*/$1/

不知道Bash中是否也有類似的緊湊的寫法。一個比較冗長的寫法是這樣的:

i='abcd123'
re='([a-z]*).*'
[[ $i =~ $re ]]
echo ${BASH_REMATCH[1]}

嘗試高橋流

Sunday, November 16th, 2008

用Openoffice.org Impress做了個高橋流的簡報。(其實是偽高橋流,正宗高橋流字體更大,圖片比較少。)

Records of the Grand Historian.

[Haskell筆記]符號

Sunday, November 16th, 2008

上一節中你已經做了不少算術題吧?再來一道何如?

求半徑為5cm的圓的面積。

圓面積公式為s = πr2,所以我們在GHCi中輸入:

Prelude> 3.14 * 5^2
78.5

答案是78.5平方厘米。這裏,我們取pi=3.14。我們可以把pi的近似值可以取得更精確些,反正是電腦替我們算。

Prelude> 3.14159265358979323846264338327950 * (5 ^ 2)
78.53981633974483

計算了面積,不妨再算一下這個圓的周長:

Prelude> 2 * 3.14159265358979323846264338327950 * 5
31.41592653589793

如果,圓的半徑是25,那面積又是多少呢?

Prelude> 3.14159265358979323846264338327950 * (25 ^ 2)
1963.4954084936207

你該對重復輸入pi的近似值感到厭煩了吧?(或者是對不斷地復制、粘貼感到厭煩。甚至,你可能已經利用上箭頭來回顧上一個命令。是的,你可以使用上箭頭,就像在Bash中一樣!)事實上,編程的意義就在於避免重復乏味的工作。在自然語言中,當一個詞過長時,我們往往會給它起個縮略名,比如,我們一直在提GHC,而不是Glasgow Haskell Compiler。有一些輸入法,比如fcitx,支持自定義詞組,以節省擊鍵。在Haskell中,我們也可以這麽做。

Prelude> let pi = 3.14159265358979323846264338327950

這裏,我們用pi來代表3.14159265358979323846264338327950。換句話說,我們引入了一個符號(symbol)pi,它的值為3.14159265358979323846264338327950。以後,當我們需要用到3.14159265358979323846264338327950的時候,就可以輸入pi。

Prelude> pi
3.141592653589793

怎麽少了幾位?別擔心,這只是為了顯示上的整潔。後面的幾位并沒有被吞掉,在以後的計算中,仍然是根據我們先前定義的值(而不是顯示出的值)。

這樣,上面的三道題就可以這樣輸入了:

Prelude> pi * (5 ^ 2)
78.53981633974483
Prelude> 2 * pi * 5
31.41592653589793
Prelude> pi * (25 ^ 2)
1963.4954084936207

接下來,也許你想用r來代表半徑5。

Prelude> let r = 25
Prelude> 2 * pi * r

咦?出錯了?

<interactive>:1:9:
    Couldn't match `Double' against `Integer'
      Expected type: Double
      Inferred type: Integer
    In the second argument of `(*)', namely `r'
    In the definition of `it': it = 2 * pi * r

怎麽回事?這裏涉及到Haskell中的一個重要概念:類型(type)。在上面這個例子中,r(25)的類型是Integer(整數),而pi的類型則是Double(浮點數)。r和pi類型不同,不能相乘。

這裏有兩個問題。首先,為什麽要有類型?如果沒有類型,或者,把類型分得粗一些,不就少了很多麻煩嗎?這個問題就說來話長了。簡單的說,類型有助於我們及時地發現編程中的錯誤。

其次,我們并沒有說pi是Double,r是Integer啊!Haskell是怎麽知道的呢?答案是:猜的!這也正是出現錯誤的原因,Haskell猜錯了我們的意圖。25可以被認為是Integer,也可以被認為是Double,在沒有其它信息的情況下,Haskell猜它是Integer。

所以,要避免這個錯誤,我們直接告訴Haskell,r是Double。那就不會出錯了。

Prelude> let r = 25 :: Double
Prelude> 2 * pi * r
157.07963267948966

那我們是否有必要告訴Haskell pi是Double呢?可以,但是沒有必要。因為上下文提供了足夠的信息,Haskell足以判斷了。事實上,大多數情況下,Haskell都能都根據語境成功推斷出類型,不用我們每次都指明類型。

也許,我們可以讓Haskell更“聰明”一些,這樣,它就能正確判斷出r應該被看作Double,而不是Integer。事實上,Haskell可以更“聰明”(GHC有-fno-monomorphism-restriction這樣一個flag(標記)),但是Haskell默認保持比較“笨”的狀態。因為更“聰明”的代價是要做更多的計算,這大大影響了程序的效率。

順便提一下,首字母大寫的字符串(包括單個大寫字母)不能用來作符號,因為它們另有它用。具體用途,以後再說。(如果你夠聰明,看到上面寫的是Double和Integer,而不是double和integer,應該已經猜到答案了。)

[Haskell筆記]基本環境配置

Saturday, November 15th, 2008

一些資源:

教程

可能是目前唯一一本適合編程零基礎者的:http://en.wikibooks.org/wiki/Haskell

本筆記就是在讀這教程基礎上寫的。

基本環境配置

我們需要一個編譯器。Haskell編譯器不止一個,其中GHC(Glasgow Haskell Compiler)是最流行,也是最完善的。

安裝GHC:

MS Windows

下載以下文件:

ghc-6.10.1-i386-windows.exe

Mac OS X

Intel上的Leopard用戶可直接下載安裝以下文件(事先需已安裝Xcode,你可在安裝光盤或蘋果開發者中心網站上找到Xcode):

GHC-6.10.1-i386.pkg

PowerPC的Leopard用戶可下載這個文件:

ghc-6.10.1-powerpc-apple-darwin.tar.bz2

PowerPC的Tiger用戶可下載這個:

ghc-6.10.1-powerpc-apple-darwin.tar.bz2

當然,Mac OS X用戶也可通過MacPorts或Fink來安裝。

Linux

你的發行版應該已經提供ghc包下載。看看你的發行版的軟件倉庫。

FreeBSD和NetBSD

可通過FreeBSDPorts或pkgsrc安裝。

從源碼編譯比較麻煩,特別是在你的機器上沒有舊版本的ghc的情況下。這裏不提。

安裝好後,可以嘗試下GHC提供的一個交互式解釋程序GHCi(i代表interactive,意為交互)。在命令行下輸入ghci即可啟動。

啟動後的屏幕輸出應該類似這樣:

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

Prelude> 是提示符,在它後面可以輸入Haskell代碼,而GHCi會作出反映。

Haskell代碼并不神秘,最簡單的Haskell代碼一看就懂,比如,輸入"2 + 2",然後回車。

Prelude> 2 + 2
4
Prelude>

再嘗試一些其他的算術表達式:

Prelude> 5 * 4 + 3
23
Prelude> (1 + 1) ^ 5
32

你可以再嘗試一些其它的算術題。如何?GHCi至少可以當計算器用。:-)

玩夠了,輸入:q退出。