天天看點

Haskell 程式設計入門設定Cabal

在過去的幾個月裡,學習Haskell讓我覺得非常快樂,但是入門的過程并沒有我原先想象的那麼簡單。我非常幸運地在一個正确的地方工作,并且是以能夠在Facebook參加 Bryan O'Sullivan 的Haskell課程。在 Try Haskell 上玩了一段時間後,最終你就會想要在自己的電腦上安裝GHC了。

Haskell 程式設計入門設定Cabal

設定Cabal

Cabal

是Haskell用于建構應用程式和庫的公共架構。和其配套使用的工具包是

Hackage

,作用則類似于Perl的CPAN,Python的pip或者Ruby的gem。你可能對其表示失望,但是它也不至于糟糕。

當最終安裝完cabal包時,預設會把它安裝到

~/.cabal/

目錄,腳本則安裝到 ~/.cabal/bin目錄下。緊接着,還要添加環境路徑。以下設定即可滿足,但也可以按照你自己喜好來設定shell的profile: 

echo 'export PATH=$HOME/.cabal/bin:$PATH'>> ~/.bashrc

在使用cabal工作之前,你還需要更新可用包清單。這隻是偶爾是使用到而已,特别在安裝或者更新新的包時。

cabal update

這時,~/.cabal/config 檔案的library-profiling 選項還沒開啟。如果你現在不開啟,以後再開啟則可能需要重新編譯所有的檔案。開啟此選項,需要編輯~/.cabal/config檔案,并且設定--library-profiling: Falsetolibrary-profiling: True。

$forfin ~/.cabal/config;

do

  cp $f $f.old && sed-E's/(-- )?(library-profiling: )False/\2True/'< $f.old > $f;

done

在安裝其他東西前,還需要安裝 Calbal installer:

cabal install cabal-install

安裝ghc-mod(更好的Emacs/Vim的支援)

ghc-mod

是在Emacs和Vim中內建GHC的環境.你也許能夠從

SublimeHaskell

中使用Sublime

Text2和ghc-mod.我至今隻試過在Emacs中使用過它.Vim使用者可以嘗試

hdevtools

,它編譯更加迅速并且同樣準确(參考

kamatsu's comment

).

$ cabal install ghc-mod

顯然你必須為你的Emacs配置它,并且我會将我現在的

~/.emacs.d

留給你做參考.

安裝 Cabal-dev(建構沙盒的工具)

Cabal-dev是一個能幫助你安裝Haskell軟體的工具。它類似于Python 的 virtualenv或者Ruby的rvm,但是使用上有些不同。Cabal-dev 能讓你免于“Calbal Hell”(即你現在安裝的包和你之前安裝的包存在依賴沖突)的煩惱。

盡量使用calbal-dev 代替僅僅使用cabal進行編譯工作。其主要代價是花費多一點時間編譯那些已經安裝在某處的包(和浪費一些磁盤空間),不過這也是相當劃算的了。

使用源碼編譯和安裝Cabal-dev:

如果你想嘗試一些工具,但是并不想污染你的haskell環境,你可以使用cabal-dev。cabal-dev預設的沙盒在 ./cabal-dev,但是你可以把它放到任何地方。下面的例子中, 我将安裝

darcs

2.8.2 (一個haskell寫的版本控制系統)到/usr/local/Cellar/darcs/2.8.2中,并使用Homebrew建立符号連結。在其他平台你可能希望使用自己指定的路徑和PATH環境變量。

$ cabal-dev install -s /usr/local/Cellar/darcs/2.8.2 darcs-2.8.2

$ brew link --overwrite darcs

OK!現在darcs已經在你的PATH中了,而且你并不需要擔心版本沖突。好吧,你沒辦法完全擺脫它們,但是沒有以前那麼多了。特别注意,cabal-dev會将所有包安裝在沙盒頂層目錄。這意味着如果兩個包有着相同的依賴關系(完全相同),它們會互相破壞它們内部指向依賴的包内的檔案的符号連結,例如釋出協定檔案和文檔。這樣使用--overwrite可能沒有什麼損失,但是你最好先用--overwrite --dry-run選項檢查一番。有些麻煩,但是不會毀掉你一整天的成果。

如果你想看看你所安裝的darcs的版本,使用cabal info darcs。

其他有意思的haskell寫的工具(不分先後):

  • pandoc - 一個像瑞士軍刀一樣的标記語言轉換器(例如,markdown, reStructuredText, org-mode, LaTeX)
  • gitit - 使用git, darcs或者mercurical作倉庫的wiki
  • pronk - 一個HTTP負載測試工具,像ab或者httperf,隻是相比更現代和易用

配置GHCi

GHCi是GHC互動式解釋器的縮寫。更詳細的文檔請參考

GHC使用者指南

第二章:使用GHCi

)。你将在編碼過程中花上很長一段時間,你大概想要設定一個更短的提示符。它剛開始的時候看起來像這樣:

Prelude>

一旦你開始引入子產品,提示符将不斷變長。真的,你一生都不需要這麼長的提示符。

Prelude>:m+Data.List

Prelude Data.List>:m+Data.Maybe

Prelude Data.List Data.Maybe>

配置檔案是

.ghci

。我用一個非常簡單的ASC II提示符,而另一些人喜歡把提示符設定成諸如"λ>"這樣。

echo':set prompt "h> "'>> ~/.ghci

Hello World:

$ ghci

h>putStrLn"Hello World!"

Hello World!

h>

官方對Hackage支援還很不足,但其也有非官方鏡像.

令人遺憾的是,目前Hackage還不是很穩定。我不知道什麼原因造成的,但希望他們能盡早做點什麼。Hackage有其工作區,你可以使用

hdiff.luite.com

或者

hackage.csc.stanford.edu

的倉庫。

修改 ~/.cabal/config 檔案行為:

remote-repo: hackage.haskell.org​:

http://hackage.haskell.org/packages/archive

變成這樣:

-- TODO When hackage is back up, set back to hackage.haskell.org!

-- remote-repo: hackage.haskell.org: http://hackage.haskell.org/packages/archive

remote-repo: hdiff.luite.com: http://hdiff.luite.com/packages/archive

-- remote-repo: hackage.csc.stanford.edu: http://hackage.scs.stanford.edu/packages/archive

當你完成修改遠端倉庫的設定後,你将需要更新包清單:

不要忘記後續把它還原回來!

開始一個項目(使用cabal-dev)

你最終會明白如何去做,但是一個快速的方法是直接使用cabal-dev。下面告訴你開始一個簡單的程式。

對于你自己的項目,你可能想要去掉-n标志,讓cabal-dev詢問你你想要的選項。-n會使用所有預設的選項而沒有提示。

$ mkdir -p ~/src/hs-hello-world

$ cd ~/src/hs-hello-world

$ touch LICENSE

$ cabal init -n --is-executable

這回生成一個Setup.hs檔案和hs-hello-world.cabal檔案。下面需要修改main-is這行來說明你要從哪個檔案編譯你的可執行檔案。最終的檔案可能像這樣。

hs-hello-world.cabal

-- Initial hs-hello-world.cabal generated by cabal init.  For further

-- documentation, see

http://haskell.org/cabal/users-guide/

name:               hs-hello-world

version:            0.1.0.0

-- synopsis:          

-- description:        

license:            AllRightsReserved

license-file:       LICENSE

-- author:            

-- maintainer:        

-- copyright:          

-- category:          

build-type:         Simple

cabal-version:      >=1.8

executable hs-hello-world

  main-is:            HelloWorld.hs

  -- other-modules:      

  build-depends:      base ==4.5.*

然後建立一個HelloWorld.hs,可能像這樣:

HelloWorld.hs

main :: IO()

main = putStrLn "Hello, world!"

你可以編譯并安裝它到一個本地的沙盒:

$ cabal-dev install

Resolving dependencies...

Configuring hs-hello-world-0.1.0.0...

Building hs-hello-world-0.1.0.0...

Preprocessing executable'hs-hello-world'forhs-hello-world-0.1.0.0...

Installing executable(s)in/Users/bob/src/hs-hello-world/cabal-dev//bin

Installed hs-hello-world-0.1.0.0

$ ./cabal-dev/bin/hs-hello-world

Hello, world!

生成的檔案比較大,因為他是靜态連接配接的。你可以複制它到任何處理器架構和作業系統相同的機器上,它可以直接運作。

你可以跳過安裝步驟,這樣可能節省一點時間:

$ cabal-dev configure

$ cabal-dev build

[1 of 1] Compiling Main             ( HelloWorld.hs, dist/build/hs-hello-world/hs-hello-world-tmp/Main.o )

Linking dist/build/hs-hello-world/hs-hello-world...

$ ./dist/build/hs-hello-world/hs-hello-world

既然這個項目沒有什麼依賴,你可跳過一些步驟。

可以解釋執行它,不需要編譯:

$ runghc HelloWorld.hs

GHCi, version7.4.2: http://www.haskell.org/ghc/ :? for help

Loading package ghc-prim...linking...done.

Loading package integer-gmp...linking...done.

Loading package base...linking...done.

Prelude>:load HelloWorld

[1of1] Compiling Main             ( HelloWorld.hs, interpreted )

Ok, modules loaded: Main.

*Main>main

你甚至不需要用cabal-dev(cabal)編譯它:

$ runghc Setup.hs configure

$ runghc Setup.hs build

但是對一些複雜的項目,你可以使用cabal-dev ghci(在cabal-dev configure &&

cabal-dev build之後)。注意它會自動裝載你的可執行檔案到解釋器裡:

$ cabal-dev ghci

on the commandline:

    Warning:-O conflicts with--interactive;-O ignored.

h> main

GHCi基本使用

更多你想知道的GHCi用法可以在

Chapter 2. Using GHCi

找到。

****:t 顯示一個表達式的類型簽名

λ >> :t main

main :: IO ()

λ >> :t map

map :: (a -> b) -> [a] -> [b]

λ >> :t map (+1)

map (+1) :: Num b => [b] -> [b]

:i 顯示一個符号的資訊(函數,typeclass,類型等等)

λ >> :i Num

class Num a where

  (+) :: a -> a -> a

  (*) :: a -> a -> a

  (-) :: a -> a -> a

  negate :: a -> a

  abs :: a -> a

  signum :: a -> a

  fromInteger :: Integer -> a

    -- Defined in `GHC.Num'

instance Num Integer -- Defined in `GHC.Num'

instance Num Int -- Defined in `GHC.Num'

instance Num Float -- Defined in `GHC.Float'

instance Num Double -- Defined in `GHC.Float'

λ >> :info map 

map :: (a -> b) -> [a] -> [b]  -- Defined in `GHC.Base'

λ >> :info Int

data Int = GHC.Types.I# GHC.Prim.Int#   -- Defined in `GHC.Types'

instance Bounded Int -- Defined in `GHC.Enum'

instance Enum Int -- Defined in `GHC.Enum'

instance Eq Int -- Defined in `GHC.Classes'

instance Integral Int -- Defined in `GHC.Real'

instance Ord Int -- Defined in `GHC.Classes'

instance Read Int -- Defined in `GHC.Read'

instance Real Int -- Defined in `GHC.Real'

instance Show Int -- Defined in `GHC.Show'

:m 增加一個子產品

λ >> :m +Data.List

λ >> sort [10, 9..1]

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

:l 導入一個子產品,:r 重新導入子產品

λ >> :! echo 'hello = print "hello"' > Hello.hs

λ >> :l Hello

[1 of 1] Compiling Main             ( Hello.hs, interpreted )

λ >> hello 

"hello"

λ >> :! echo 'hello = print "HELLO"' > Hello.hs

λ >> :r

"HELLO"