热门问题
时间线
聊天
视角

Haskell

函數式編程語言 来自维基百科,自由的百科全书

Remove ads

Haskell(發音為/ˈhæskəl/[26]是一種標準化的,通用的純函數式程式語言,有惰性求值靜態類型[27]。它的命名源自美國邏輯學家哈斯凱爾·加里,他在數理邏輯方面上的工作使得函數式程式設計語言有了廣泛的基礎。在Haskell中,「函數是頭等物件[28]。作為一門函數程式語言,主要控制結構是函數。Haskell語言是1990年在程式語言Miranda語言的基礎上標準化的,並且以λ演算為基礎發展而來。這也是為什麼Haskell語言以希臘字母λ」(Lambda)作為自己的標誌。Haskell具有「證明即程式、命題為類型」的特徵[29][30][31][32]

快速預覽 編程範型, 設計者 ...
Remove ads
Remove ads

歷史

1985年,Miranda發行後,惰性函數式語言的關注度增長。到1987年前,出現了十多種非限定性英語Strict function、純函數式語言。其中,Miranda使用的最為廣泛,但還沒有出現在公共領域。在俄勒岡波特蘭的函數式程式設計語言與電腦結構大會(FPCA '87)上,參加者一致同意形成一個委員會來為這樣的語言定義一種開源標準。該委員會旨在整合已有函數式語言,作為將來的函數式語言設計研究工作的基礎。[33]

Haskell 1.0至1.4

能夠確使類型安全運算子多載類型類,是由Philip Wadler和Stephen Blott最初為Standard ML提議的,但首先在Haskell中於1987年至版本1.0期間實現[34][35]

1990年定義了Haskell的第一個版本(「Haskell 1.0」)。[36]委員會形成了一系列的語言定義(1.0,1.1,1.2,1.3,1.4)。

Thumb
在GHC 7.10的Haskell prelude中的類型類層級。包括了Foldable和Traversable(相應的改變了一些函數的類型簽章),和作為FunctorMonad中介的Applicative,它們出自Haskell 2010標準。

Haskell 98

1997年底,該系列形成了Haskell 98,旨在定義一個穩定、最小化、可移植的語言版本以及相應的標準庫,以用於教學和作為將來擴充的基礎。委員會明確歡迎建立各種增加或整合實驗性特性的Haskell 98的擴充和變種。[33]

1999年2月,Haskell 98語言標準公布,名為《The Haskell 98 Report》。[33]2003年1月,《Haskell 98 Language and Libraries: The Revised Report》公布。[37]接着,格拉斯哥Haskell編譯器實現了當時的事實標準,Haskell快速發展。

Haskell Prime

2006年早期,開始了定義Haskell 98標準後續的行程,非正式命名為Haskell Prime[38]這是個修訂語言定義的不斷增補的過程,每年產生一個新的修訂版。第一個修訂版於2009年11月完成、2010年7月發布,稱作Haskell 2010

Haskell 2010

Haskell 2010加入了外部函數介面(Foreign Function Interface英語Foreign Function Interface,FFI)允許繫結到其它程式語言,修正了一些語法問題(在正式語法中的改動)並廢除了稱為「n加k模式」(換言之,不再允許形如fact (n+1) = (n+1) * fact n的定義)。引入了語言級編譯選項語法擴充(Language-Pragma-Syntax-Extension),使得在Haskell原始碼中可以明確要求一些擴充功能。Haskell 2010引入的這些擴充的名字是DoAndIfThenElse、HierarchicalModules、EmptyDataDeclarations、FixityResolution、ForeignFunctionInterface、LineCommentSyntax、PatternGuards、RelaxedDependencyAnalysis、LanguagePragma、NoNPlusKPatterns。

Remove ads

特性

Haskell是現有的一門開放的、已發布標準的,且有多種實現的語言。[37]支援惰性求值模式匹配列表解析類型類類型多型。它是一門純函數式程式設計語言,這意味着大體上,Haskell中的函數沒有副作用。Haskell用特定的類型來表達副作用,該類型與函數類型相互獨立。純函數可以操作並返回可執行的副作用的類型,但不能夠執行它們,只有用於表達副作用的類型才能執行這些副作用,Haskell以此表達其它語言中的非純函數。

Haskell擁有一個基於Hindley-Milner類型推論靜態強型別系統。Haskell在此領域的主要創新就是加入了類型類,原本設想作為多載的主要方式,[39]在之後發現了更多用途。[40]

Haskell的主要實現GHC是個直譯器,也是個原生代碼編譯器。它可以在大多數平台執行,GHC在並行和並列上具有高效能的實現能力,[41]也有豐富的型別系統,如廣義代數資料類型英語Generalized algebraic data type類型家族英語Type family)。

單子是一個抽象類型,可以表達不同種類的計算,包括例外處理非確定性語法分析以及軟件事務主記憶體,其中一個應用是用於表達副作用的類型。單子定義為普通的資料類型,同時Haskell也為其提供了幾種語法糖

Haskell有一個活躍的社區,線上上包倉庫Hackage上有豐富的第三方開源庫或工具。[42]

Remove ads

語法

資料類型

Haskell是強型別語言。Char的字面值用單引號圍起;字串即[Char]類型,其字面值用雙引號括起來。Int通常為32位元整型;Integer是無界整型;Float 表示單精度的浮點數;Double 表示雙精度的浮點數;Bool 只有兩種值:True 和 False。

List

使用[ ]與逗號分隔符,定義一個list的實例。其元素必須具有相同類型。字串是list的特例。用:把元素與list、其他元素連接(cons)起來。:是右結合的運算子。[1,2,3] 實際上是 1:2:3:[] 的語法糖。兩個 List 合併通過 ++ 運算子實現。按照索引取得 List 中的元素,可以使用 !! 運算子,索引的下標為 0。List 中的 List 可以是不同長度,但必須得是相同的型別。['K'..'Z']這樣的Range方法快捷定義一個List。[2,4..20]用法給出了Range的第一、第二、最後一個元素。使用 > 和 >= 可以比較 List 的大小。它會先比較第一個元素,若它們的值相等,則比較下一個,以此類推。List常用的函數:

  • head 返回一個 List 的頭部,也就是 List 的首個元素。
  • tail 返回一個 List 的尾部,也就是 List 除去頭部之後的部分。
  • last 返回一個 List 的最後一個元素。
  • init 返回一個 List 除去最後一個元素的部分。
  • length 返回一個 List 的長度。
  • null 檢查一個 List 是否為空。如果是,則返回 True,否則返回 False。
  • reverse 將一個 List 反轉
  • take 返回一個 List 的前幾個元素。例如take 24 [13,26..]取前24個13的倍數
  • drop 刪除一個 List 中的前幾個元素
  • maximum 返回一個 List 中最大的元素。
  • minimun 返回最小的元素。
  • sum 返回一個 List 中所有元素的和。
  • product 返回一個 List 中所有元素的積。
  • elem 判斷一個元素是否在包含於一個 List,通常以中綴函數的形式呼叫
  • replicate 得到包含相同元素的 List 。例如:replicate 3 10,得 [10,10,10]。
  • repeat 產生一個元素的無限重複的List
  • cycle 接受一個 List 做參數並返回一個無限 List

list comprehension是指基於一個List,按照規則產生一個新List,例如:[x*2 | x <- [1..10], x*2 >= 12]

Tuple

使用( )與逗號分隔符,定義一個tuple的實例。其元素可以使不同類型,但個數是固定的。

Tuple的類型取決於其中項的數目與其各自元素的類型。單元素的 Tuple 被禁止。

  • fst 返回一個序對的首項。
  • snd 返回序對的尾項
  • zip 取兩個 List作為參數,然後將它們依次配對,形成一組序對的 List。

運算子

基本類似於C語言。但使用not表示邏輯非。

 +  -  *  /  ^    -- 加、減、乘、除、指數
 mod              -- 取餘數
 $                -- 也是表示函數作用的, 但它的優先級最低, 而且作用次序是從右向左的
 ++               -- 兩個List的連接
 .                 -- 函數的複合
 && ||  ==  /=     -- 與、或、等於、不等於
 <=  >=  <  >      -- 小於等於、大於等於、小於、大於
 : //  =  @        -- 一個元素連接List、
 ->               -- 函數類型描述,運算符左邊為參數類型,右邊為結果類型。為右结合。例如:addThree :: Int -> Int -> Int -> Int 
 =>               -- 運算符的左邊表示一個類型變量(通常為單個小寫字母)屬於一個類型類(Typeclass),相當於C++語言的模板參數類型
 ..               -- List的Range限定
 ::               -- 函數/表達式的類型特徵,左側為函數/表達式,右側為類型
 <-             -- List comprehension 的條件限定
 !!             -- 取List的第某個元素,下標從0開始

基本的 Typeclass:

  • Eq 可判斷相等性的type
  • Ord 可比較大小的type
  • Show 可表示為字串的type
  • Read 可從字串轉換出值的type
  • Enum 連續的,也就是可列舉的type。每個值都有後繼 (successer) 和前置 (predecesor),分別可以通過 succ 函數和 pred 函數得到。
  • Bounded 有上限和下限。例如:maxBound :: Char 或者 maxBound :: Bool
  • Num 數字
  • Integral
  • Floating
Remove ads

表達式

  • let表達式:格式為 let [bindings] in [expressions] 。let 也可以定義局部函數 。在一行中設定多個名字的值,可用分號隔開。List Comprehension 中 let繫結的名字在輸出函數及限制條件中都可見;忽略了 let 繫結的 in 部分,因為名字的可見性已經預先定義好了。
  • if then else是表達式
  • Case表達式:
 case expression of pattern -> result  
      pattern -> result  
      pattern -> result  
      ...  

控制結構

if then else是分段函數定義時的語法糖。與C語言不同,要求必須有else部分。類似於C語言分支陳述式的情形,叫做pattern matching,例子如下:

pts :: Int -> Int
pts 1 = 10
pts 2 = 6
pts x
    | x <= 6    = 7 - x
    | otherwise = 0

(||) :: Bool -> Bool -> Bool  -- 或操作的类型与定义
True  || _ = True             -- 第二个参数是任何值都匹配。 
False || y = y

函數

  • 函數呼叫有最高運算順序,例如succ 9*10表示(succ 9)*10
  • 函數的呼叫使用空格符而不是括號。
  • 函數的複合呼叫是左結合。
  • 首字母大寫的函數是不允許的。
  • 兩個參數的函數的呼叫可以寫成中綴形式:param1 `funcName` param2
  • 運算子可以用括號圍起來,作為字首形式:(+) 2 3 的結果為5。
  • 在 ghci 下,我們可以使用 let 關鍵字來定義一個常數。在 ghci 下執行 let a=1 與在指令碼中編寫 a=1 是等價的。
--funcName arguments = expression  --定义函数的一般形式
area r = pi * r ^ 2 -- 定义了一个函数
area 101 -- 调用了函数
f1 f2 3.14 -- 函数调用是左结合,等效于(f1 f2) 3.14

--模式匹配方式定义
factorial :: (Integral a) => a -> a  
factorial 0 = 1  
factorial n = n * factorial (n - 1)   

--as模式,是将一个名字和 @ 置于模式前,可以在按模式分割参数值时仍保留对其整体的引用。如nameGlobal@(x:y:ys),nameGlobal会匹配出与 x:y:ys 对应的东西。as模式在较大的模式中保留对整体的引用,从而减少重复性的工作。

heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))
    where                     -- where在表达式中局部绑定了名字s与一个值。也可以在表达式之前用let ... in语法
    s = (a + b + c) / 2

absolute x                 -- 绝对值函数,使用了分段函数语法糖(称作Guards)
    | x < 0     = 0 - x
    | otherwise = x        -- 兜底条件

bmiTell :: (RealFloat a) => a -> a -> String  
bmiTell weight height  
    | bmi <= 18.5 = "You're underweight."  
    | bmi <= 25.0 = "You're normal. "  
    | bmi <= 30.0 = "You're fat."  
    | otherwise   = "You're overweight."  
    where bmi = weight / height ^ 2  -- 使用where定义多个名字来避免重复

--where也可以用模式匹配
initials :: String -> String -> String  
initials firstname lastname = [f] ++ ". " ++ [l] ++ "."  
    where (f:_) = firstname  
          (l:_) = lastname  

--where可以定义函数。在定义一个函数的时候也写几个辅助函数摆在 where 绑定中。 而每个辅助函数也可以透过 where 拥有各自的辅助函数。
calcBmis :: (RealFloat a) => [(a, a)] -> [a]  
calcBmis xs = [bmi w h | (w, h) <- xs] 
    where bmi weight height = weight / height ^ 2  

funcName :: type1 -> type2 -> type3 -- 其中,::表示类型特征(type signature),->是右结合,这里等效于type1 -> (type2->type3),给定一个type1的输入参数,返回一个函数(type2->type3)

f1 = (absolute .  area)   -- 函数复合运算符是 . (function composition operator)

多型類型(Polymorphic types)類似於C++的模板。例如,算術加法:

 (+) :: (Num a) => a -> a -> a -- Num是typeclass。 =>表示signature restricts

lambda函數

lambda 就是匿名函數。寫法是:一個 \(因為它看起來像是希臘字母λ),後面是用空格分隔的參數,-> 後面是函數體。通常用括號將括起lambda函數,否則它會佔據整個右邊部分。

例如:(\a b -> (a * 30 + 3) / b)

可以在 lambda 中使用模式匹配,但無法為一個參數設置多個模式,如 [](x:xs) 並用。

使用 lambda 可以更明確地表現出值是個函數,可以用來傳遞給其他函數作參數。

高階函數

Haskell的所有函數實際上是單參數函數。多參數函數的寫法實際上是Curry化的語法糖。即 func a b 等價於 (func a) b

point free style (也稱作 pointless style) 的函數,即通過柯里化 (Currying)省略掉單參數。例如:

 sum' :: (Num a) => [a] -> a     
 sum' xs = foldl (+) 0 xs   --等号的两端都有个 xs。 
 sum'   = foldl (+) 0     --柯里化 (Currying),可以省掉两端的 xs。

中綴運算子可以加括號變為單參數函數。如 (*3) 5 的值為15。 但 (-5) 表示負值,所以單參數函數需要寫為 (subtract 5)

中綴運算子 $ 可用於改變函數的呼叫次序,使其右邊的表達式先計算出來。這可以減少一對括號使用。例如 f (g (z x))f $ g $ z x 等價。其定義是:

 ($) :: (a -> b) -> a -> b  
 f $ x = f x

$ 還可以將數據作為函數使用。例如:

 map ($ 3) [(4+),(10*),(^2),sqrt]

中綴運算子 . 用於函數的複合,其定義是:

 (.) :: (b -> c) -> (a -> b) -> a -> c  
 f . g = \x -> f (g x)

例外處理

提供了處理異常的函數trycatchfinally/etc.

import Prelude hiding(catch)
import Control.Exception
instance Exception Int
instance Exception Double
main = do
  catch
    (catch
      (throw (42::Int))
      (\e-> print (0,e::Double)))
    (\e-> print (1,e::Int))

輸出結果

(1,42)

類似於 C++

#include <iostream>
using namespace std;
int main() {
    try {
        throw (int)42;
    } catch (double e) {
        cout << "(0," << e << ")" << endl;
    } catch (int e) {
        cout << "(1," << e << ")" << endl;
    }
}

另外一個例子:

do {
  -- Statements in which errors might be thrown
} `catch` \ex -> do {
  -- Statements that execute in the event of an exception, with 'ex' bound to the exception
}

如果僅有一個錯誤條件,Maybe 類足夠用了,確省是Haskell的 Monad class. 更複雜的出錯處理用ErrorErrorT monads, 類似功能可用`catch`

範例

Haskell的函數結構

如下是Haskell語言的"Hello world",注意其中除最後一行外皆可省略。

module Main where

main :: IO ()
main = putStrLn "Hello, World!"

如下是階乘函數的Haskell實現:

fac :: Int -> Int
fac 0 = 1
fac n = n * fac (n - 1)

它將階乘描述成有一個基本終止情形的遞歸函數。這跟數學定義中對階乘的描述很相似。事實上,Haskell中很多的代碼的語法與功能都和數學一致。

上面的遞歸函數的第一行是可選的,它描述了這個函數的型態types)。它可以讀作函數fac的型態為整數至整數(function fac has a int-to-int type)。這就是說,它以一個整型為參數,並且返回另一個整型。

第二行依賴的模式匹配,是Haskell程式中一個重要的部分。注意函數的參數是用空格分隔而不是在括號中。當函數的參數是0時,它會返回整型1。對於其他的情況則嘗試第三行。這是一個遞歸,它會一直執行只到滿足基本的情形。負參數會導致無限遞歸,一個guard英語guard (computing)保證第三行不會執行負參數。

"Prelude"是一個類似C中標準庫的小函數集合。使用Prelude,並用無指定參數的寫法,它可以改成:

fac = product . enumFromTo 1

上面的定義接近於數學中的定義:f = g o h(參見複合函數),這並不是一個對變量賦值的陳述式。

Haskell的高階函數

Haskell中可以定義高階函數(Higher-order Function),既將函數作為一個參數來使用,也可以將函數作為結果輸出,例如:

f :: (Int -> Int) ->(Int -> Int)
f g = \x -> g x + 5

這裏f就是一個高階函數,它取一個從Int到Int的函數g作為參數,輸出一個從Int到Int的函數。高階函數的使用在一些情況下將極大的簡化代碼。

Haskell的編譯器

參考文獻

外部連結

Loading related searches...

Wikiwand - on

Seamless Wikipedia browsing. On steroids.

Remove ads