chapter2 類型和函數

Haskell的類型系統

  • 強類型:類型轉換必須顯示使用類型轉換函數。
  • 靜態:不是python這種動態語言。java這樣的編譯時期就能發現類型不正確的表達式。Haskell提供的typeclass機制提供了大部分動態類型的特點。
  • 可以通過自動推導得出

一些常用的基本類型

  • Char: 單個Unicode字符
  • Bool:一個布爾邏輯值。只有2個值。 True和False
  • Int:帶符號的定長整數
  • Integer: 不限長度的帶符號整數。對Integer的計算不會造成溢出。因此使用Integer的計算結果更可靠。
  • Double:浮點數,長度由機器決定,通常是64位。
    :: 這個符號用來標識類型.例如:: T 就是像Haskell表示,exp的類型是T. :: T 就是exp的類型簽名。如果一個表達式沒有顯示地指名類型,它的類型就通過自動推導決定。
Prelude> :type 'a'
'a' :: Char

Prelude> 'a'            -- 自動推導
'a'

Prelude> 'a' :: Char    -- 顯式簽名
'a'

類型簽名必須正確。

Prelude> 'a' :: Int     -- 試圖將一個字符值標識為 Int 類型

<interactive>:7:1:
    Couldn't match expected type `Int' with actual type `Char'
    In the expression: 'a' :: Int
    In an equation for `it': it = 'a' :: Int

調用函數

要調用一個函數,首先寫出它的名字.后面接函數的參數

Prelude> odd 3
True

Prelude> odd 6
False

注意.函數的參數不需要括號來包圍,參數與參數直接直接通過空格來隔開.

Prelude> compare 2 3
LT

Prelude> compare 3 3
EQ

Prelude> compare 3 2
GT

函數應用的優先級比操作符要高.

Prelude> (compare 2 3) == LT
True

Prelude> compare 2 3 == LT
True

復合數據類型:列表和元組

復合類型通過其他類型構建得出.Haskell中最常用的是復合類型數據是列表跟元組.

  • String 是[char]的別名,[Char]表示由Char類型組成的列表.
    因為列表中的值可以是任意類型,所以列表是類型多態的.當編寫帶有多態類型的代碼時,需要使用類型變量.這些類型變量用小寫字母開頭,作為一個占位符,最終被一個具體的類型替代(有點像java的泛型)
    [a] 表示一個'類型為a的列表'
  • 元組:跟列表的兩個屬性相反,元組長度固定,但可以包含不同類型的值.
    Haskell 有一個特殊的類型(),這種類型只有一個值(),它的作用相當于包含零個元素的元組,類似于C語言中的void:
Prelude> :t ()
() :: ()

元組通常用于以下兩個地方:

  • 如果一個函數需要返回多個值,可以將這些值包裝到一個元組中,然后返回元組作為函數的值.
  • 當需要使用定長容器,又沒必要使用自定義的類型時,就可以使用元組來對值進行包裝.

處理列表和元組的函數

  • take和drop.接受兩個參數,一個數字n和一個列表l.
Prelude> take 2 [1, 2, 3, 4, 5]
[1,2]
Prelude> drop 2 [1, 2, 3, 4, 5]
[3,4,5]
  • fst,snd接受一個二元組作為參數,返回該元組的第一個和第二個元素。
Prelude> fst (1, 'a')
1

Prelude> snd (1, 'a')
'a'

將表達式傳給函數

Haskell函數應用是左關聯的。比如說,表達式a b c d 等同于(((a b) c) d)。要將一個表達式用作另一個表達式的參數。必須顯式的使用括號來包圍它。

Prelude> head (drop 4 "azety")
'y'

函數類型

使用 :type命令可以查看函數的類型[縮寫形式為:t]

Prelude> :type lines
lines :: String -> [String]

通過類型簽名顯示,lines函數接受單個字符串,并返回包含字符串值的列表。

純度

副作用:假設有某個函數,它讀取并返回某個全局變量,如果程序中的其他代碼可以修改這個全局變量,那么這個函數的返回值就取決于這個全局變量在某一時刻的值。
Haskell的函數在默認情況下都是無副作用的。函數的結果只取決于顯式傳入的參數。
帶副作用的函數稱為“不純(impure)函數”。不帶副作用的函數稱為“pure函數“
從類型簽名可以看出一個Haskell函數是否帶有副作用 -不純的函數類型簽名都以IO開頭:

Prelude> :type readFile
readFile :: FilePath -> IO String

Haskell源碼,以及簡單函數的定義

ghci只支持Haskell特性的一個非常受限的子集。因此,將代碼寫在源碼文件里。
Haskell源碼通常以.hs作為后綴.我們創建一個add.hs文件,并將以下定義添加到文件中:

-- file: ch02/add.hs
add a b = a + b

=號左邊的add a b是函數名和函數參數,而右邊的a+b則是函數體,符號=表示將左邊的名字(函數名和函數參數)定義為右邊的表達式(函數體)。
將add.hs保存后,可以在ghci里通過: load (縮寫為:l)載入。然后直接調用add函數即可。
注意,Haskell里一個函數就是一個單獨的表達式,而不是一組陳述。所以不適用return關鍵字來返回函數值。

變量

  • 在Haskell中,一旦變量綁定了某個表達式,name這個變量的值就不會改變,我們總能用這個變量來指代它所關聯的表達式,而且每次都會得到同樣的結果
  • 在聲明式語言中,變量的值可能無時無刻都處在改變當中。

條件求值

Haskell中的if表達式。
首先看自己實現的一個drop函數

myDrop n xs = if n <= 0 || null xs
              then xs
              else myDrop (n - 1) (tail xs)

if 關鍵字引入了一個帶有三個部分的表達式:

  • 跟在if之后的是一個Bool類型表達式,是if的條件部分
  • then 之后的是另一個表達式。這個表達式表示在if條件為
    True時執行
  • else之后的是另一個表達式,在條件部分是False時被執行。
  • 跟在thenelse之后的表達式稱為"分支".不同分支之間的類型必須相同.如果是if True then 1 else "foo"這樣的表達式會產生錯誤,因為兩個分支的類型并不相同。

通過示例了解求值

惰性求值

isOdd n = mod n 2 == 1

一般而言,使用上面的isOdd函數計算isOdd (2+1),都是先計算2+1 = 3 然后在用isOdd的函數定義來進行判斷.
但是在Haskell中.并不需要先計算出(2+1).只用把這個表達式存在塊中.當這個子表達式的值真正被需要時才進行計算.

Haskell里的多態.

觀察last函數.這個函數返回列表中的最后一個元素,不管列表中元素的類型是int或者string.
last函數簽名:

last :: [a] -> a

a可以指代任意類型.

?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。

推薦閱讀更多精彩內容