Haskell2010中文報告

維基教科書,自由的教學讀本

歡迎喜歡Haskell的朋友共同翻譯這份報告。英文版在這裡


Simon Marlow (編著)

版權聲明 本報告的出版者和發布者授意報告屬於整個Haskell社區,如果包括這份聲明在內的內容被完整的複製,那麼它將被授權做拷貝及分發,並不限拷貝或分發行為的目的。報告的修訂版本也可以被自由的拷貝及分布,但必須明確表明這是修改版本,而不是Haskell 2010語言的一個定義。


目錄

前言[編輯]

「Some half dozen persons have written technically on combinatory logic, and most of these, including ourselves, have published something erroneous. Since some of our fellow sinners are among the most careful and competent logicians on the contemporary scene, we regard this as evidence that the subject is refractory. Thus fullness of exposition is necessary for accuracy; and excessive condensation would be false economy here, even more than it is ordinarily.」

Haskell B. Curry and Robert Feys in the Preface to Combinatory Logic [3], May 31, 1956

1987年9月,在Oregon州Portland舉辦的函數式語言與計算機體系結構(FPGA)大會上有個會議,討論了當時函數式語言社區的一些問題:有超過一打的惰性、純函數式語言,但所有這些語言在表達能力與語義上都相似。會議達成了強烈的一致:如果缺乏一個共同的標準,將會妨礙這類語言的流行。會議決定組成一個委員會來設計這個通用語言,使得新的思想能夠更快的得到響應,真實的應用開發有穩固的基礎,並且能支撐更多人使用函數式語言。本文檔即介紹那次會議及隨後日子裡,委員會的工作成果:一個叫做Haskell的純函數式語言。語言的命名是為了紀念邏輯學家Haskell B.Curry ,正是他為我們提供了語言的邏輯基礎。

目標[編輯]

委員會的最初目標是設計一個能夠滿足下列約束的語言:

  1. 能適用於教學、科研以及工業應用,包括構建大型系統。
  2. 通過發布形式語法和形式語義來完全定義
  3. 能夠自由獲取。任何人都有權利去實現語言並將其分布到有興趣的人
  4. 基於得到廣泛共識的想法
  5. 能夠減少函數式程式語言的不必要多樣性

Haskell2010: 語言與庫[編輯]

委員會的初衷是讓haskell作為語言設計研究的基礎,而希望它的擴展或變體來集成一些實驗性的功能。

事實上,Haskell從最初版發布後就一直在衍進發展中。到1997年中期,haskell語言已經有了5個主要版本(從1.0-1.4)。在1997年Amsterdam的Haskell工作組會議上,人們決定定義一個新的穩定變體,這就是"haskell98",於1999年2月發布。bug修訂版本在2002年成為「修訂版haskell98報告「。

在2005年的hasekll工作組會議,人們又達成一致:官方語言的一些擴展被廣泛使用(並被多個實現支持),是時候定義新一代的語言標準並將其編纂成冊,反映當今Haskell的發展。

於是,Haskell的主要工作可以理解為相對保守地擴展Hasekll 98, 僅吸納一些透徹理解、一致認可的功能特色。這也是為了得到一個"穩定「的語言,同時又能體現這些年來在語言設計研究上的大量進展。

通過這些年來在設計上的經驗,人們認識到對語言做一次性的大修改,任務量巨大,很難一下完成。最好的辦法是逐步地、增量地修改,每一個版本都只集成已經透徹理解的擴展和修改。Haskell2010是這種工作方式下的第一個版本,新的版本將逐年發布。

Haskell98 擴展[編輯]

下面列出Haskell 2010相對於Haskell 98最顯著的修改:

  • 外部函數接口
  • 層次化模塊命名,比如,Data.Bool
  • 模式保護

移除的語言功能:

  • (n+k)模式語法

Haskell資源[編輯]

Haskell的WEB站點[1]給出了許多有用資源的連結,包括:

  • 語言和庫定義的在線版本
  • Haskell的教程資源
  • Haskell郵件列表細節
  • Haskell實現
  • Haskell工具和庫的貢獻
  • Haskell的應用
  • 用戶貢獻的wiki主頁
  • Haskell的新聞和事件

語言構建[編輯]

活躍在社區裡的研究者和應用程式員們共同創建了Haskell,並不斷維護。而這其中,服務於語言委員會和庫委員會的人們,貢獻了巨大的時間和精力。下面列出這些貢獻者以及他們的工作單位(時任)

  • Arvind (MIT)
  • Lennart Augustsson (Chalmers University)
  • Dave Barton (Mitre Corp)
  • Brian Boutel (Victoria University of Wellington)
  • Warren Burton (Simon Fraser University)
  • Manuel M T Chakravarty (University of New South Wales)
  • Duncan Coutts (Well-Typed LLP)
  • Jon Fairbairn (University of Cambridge)
  • Joseph Fasel (Los Alamos National Laboratory)
  • John Goerzen
  • Andy Gordon (University of Cambridge)
  • Maria Guzman (Yale University)
  • Kevin Hammond [editor] (University of Glasgow)
  • Bastiaan Heeren (Utrecht University)
  • Ralf Hinze (University of Bonn)
  • Paul Hudak [編輯] (Yale University)
  • John Hughes [編輯] (University of Glasgow; Chalmers University)
  • Thomas Johnsson (Chalmers University)
  • Isaac Jones (Galois, inc.)
  • Mark Jones (Yale University, University of Nottingham, Oregon Graduate Institute)
  • Dick Kieburtz (Oregon Graduate Institute)
  • John Launchbury (University of Glasgow; Oregon Graduate Institute; Galois, inc.)
  • Andres Löh (Utrecht University)
  • Ian Lynagh (Well-Typed LLP)
  • Simon Marlow [編輯] (Microsoft Research)
  • John Meacham
  • Erik Meijer (Utrecht University)
  • Ravi Nanavati (Bluespec, inc.)
  • Rishiyur Nikhil (MIT)
  • Henrik Nilsson (University of Nottingham)
  • Ross Paterson (City University, London)
  • John Peterson [編輯] (Yale University)
  • Simon Peyton Jones [編輯] (University of Glasgow; Microsoft Research Ltd)
  • Mike Reeve (Imperial College)
  • Alastair Reid (University of Glasgow)
  • Colin Runciman (University of York)
  • Don Stewart (Galois, inc.)
  • Martin Sulzmann (Informatik Consulting Systems AG)
  • Audrey Tang
  • Simon Thompson (University of Kent)
  • Philip Wadler [編輯] (University of Glasgow)
  • Malcolm Wallace (University of York)
  • Stephanie Weirich (University of Pennsylvania)
  • David Wise (Indiana University)
  • Jonathan Young (Yale University)

標示為[編輯]的人擔任協調編輯員,貢獻了語言的一個或多個修訂。

其他人也做了許多有益的貢獻,這些貢獻雖然不大,但都非常實際。下面是他們的列表: Hans Aberg, Kris Aerts, Sten Anderson, Richard Bird, Tom Blenko, Stephen Blott, Duke Briscoe, Paul Callaghan, Magnus Carlsson, Mark Carroll, Franklin Chen, Olaf Chitil, Chris Clack, Guy Cousineau, Tony Davie, Craig Dickson, Chris Dornan, Laura Dutton, Chris Fasel, Pat Fasel, Sigbjorn Finne, Michael Fryers, Peter Gammie, Andy Gill, Mike Gunter, Cordy Hall, Mark Hall, Thomas Hallgren, Matt Harden, Klemens Hemm, Fergus Henderson, Dean Herington, Bob Hiromoto, Nic Holt, Ian Holyer, Randy Hudson, Alexander Jacobson, Patrik Jansson, Robert Jeschofnik, Orjan Johansen, Simon B. Jones, Stef Joosten, Mike Joy, Wolfram Kahl, Stefan Kahrs, Antti-Juhani Kaijanaho, Jerzy Karczmarczuk, Kent Karlsson, Martin D. Kealey, Richard Kelsey, Siau-Cheng Khoo, Amir Kishon, Feliks Kluzniak, Jan Kort, Marcin Kowalczyk, Jose Labra, Jeff Lewis, Mark Lillibridge, Bjorn Lisper, Sandra Loosemore, Pablo Lopez, Olaf Lubeck, Christian Maeder, Ketil Malde, Michael Marte, Jim Mattson, John Meacham, Sergey Mechveliani, Gary Memovich, Randy Michelsen, Rick Mohr, Andy Moran, Graeme Moss, Arthur Norman, Nick North, Chris Okasaki, Bjarte M. Østvold, Paul Otto, Sven Panne, Dave Parrott, Larne Pekowsky, Rinus Plasmeijer, Ian Poole, Stephen Price, John Robson, Andreas Rossberg, George Russell, Patrick Sansom, Michael Schneider, Felix Schroeter, Julian Seward, Nimish Shah, Christian Sievers, Libor Skarvada, Jan Skibinski, Lauren Smith, Raman Sundaresh, Josef Svenningsson, Ken Takusagawa, Wolfgang Thaller, Satish Thatte, Tom Thomson, Tommy Thorn, Dylan Thurston, Mike Thyer, Mark Tullsen, David Tweed, Pradeep Varma, Keith Wansbrough, Tony Warnock, Michael Webber, Carl Witty, Stuart Wray, and Bonnie Yantis.

最後,除了Church,Rosser,Curry等的重要基礎工作以及其他人在lambda演算上的工作外,這些年其他語言的發展也影響了Haskell,一併感謝。雖然很難直接說出許多好想法的來源,但下面的語言特別影響了Haskell,它們是: lisp(以及它的現代變體common lisp, scheme),Landin的ISWIM, APL, Backus的FP, ML和Standard ML, Hope和Hope+, Clean, ID, Lofer, Sisal, 以及Turner的系列語言包括最後的miranda. 如果沒有這些先驅語言,也就沒有Haskell.


語言[編輯]

簡介[編輯]

Haskell是通用的純函數式語言,集成了當前在程式語言上的許多創新。Haskell提供高階函數、非即時語義、靜態多型類型系統、用戶定義代數類型、模式匹配、列表組合、模塊系統、單子化I/O函數、豐富的原始庫包含列表、數組、任意精度及固定精度整數、浮點數。Haskell是多年函數式程序語言最高設計水平的代表成果。

本報告定義Haskell程序的語法以及程序的非形式操作語義。

我們不關心Haskell程序的具體運行方式以及與Haskell實現相關的的問題。比如程序的解釋、編譯、變換,也包括編程環境相關的問題、無定義程序(即形式求值到⊥程序)的錯誤返回信息等。

程序結構[編輯]

本節,我們論述Haskell的抽象語法與語義結構,以及結構與報告其它部分的關係。

  1. 在最上層,Haskell是模塊的集合。模塊在模塊節論述。模塊提供命名空間,是大程序中軟體重用的主要方法。
  2. 模塊的上層包含聲明的集合。聲明有多種,在聲明和綁定節論述。聲明定義諸如常規值、數據類型、類型信息及結合信息等。
  3. 再下面一層定義表達式。表達式在表達式節論述。表達式表示一個值,並有一個靜態類型。「小範圍而言」,表達式是Haskell程序的核心。
  4. 最下層是Haskell詞法結構。詞法結構在詞法結構節論述。詞法結構是Haskell程序在文本文件中的具體表示。

本報告根據Haskell文法結構自底向上論述。

上面沒有提到的預定義類型和類節描述Haskell內建的數據類型和類型類。基本輸入輸出節討論Haskell中的I/O設施(即,Haskell怎樣與外部世界通信)。另外,還有幾節講述Prelude、具體文法、作文式編程、派生實例規範、被大多數編譯器支持的編譯器指示等。

Haskell程序樣例用印表機字體表示:

 let x = 1  
     z = x+y  
 in  z+1

」孔「,在程序樣片中表示任意大小的樣例,用斜體表示,比如if e1 then e2 else e3。通常,斜體名字有助記含義,比如e表達式expressions, d聲明declarationst類型type等。

Haskell核[編輯]

流行於許多函數式編程中的便利語法也被Haskell採用。在本報告中,這些語法糖被轉換為更簡單的結構。如果反覆應用這些轉換,程序最終可認為是Haskell的一個小子集所編寫,這個小子集我們稱為Haskell核。

雖然Haskell核沒有被形式定義,但它實際上是增加一些修飾的lambda演算變體,有直接的指稱語義。在我們介紹語法結構時,我們也給出語法結構到Haskell核的轉換。這種模塊化的設計方便我們推理Haskell程序,也為語言的實現者提供了有用的參考。

值和類型[編輯]

表達式求值到值,有靜態類型。Haskell中,值和類型並不混合。然而,類型系統允許各種用戶定義的數據類型,不僅允許參數化多型(使用傳統的Hindley-Milner類型結構),也允許臨時多型,或者稱為重載(使用類型類)。

Haskell中,錯誤和⊥("底")語義等價。技術上,這兩者看起來就像非終止,因此語言沒有檢測或處理錯誤的機制。而語言的實現通常都會對錯誤提供一些有用信息,具體請見錯誤節。

名字空間[編輯]

Haskell中,名字有6種:變量構造符表示值;類型變量類型構造符類型類是類型系統中的實體;模塊名屬於模塊系統。在命名時有兩個約束:

變量名和類型變量名以小寫字母或者下劃線開頭,其它四種名字以大寫字母開頭。

在同一作用域內,標示符不能用來做類型構造符或者類型類。

這些也僅僅只是約束:舉個例子,在同一作用域內,Int可以同時作為模塊名、類名與構造符。

詞法結構[編輯]

本章,我們討論Haskell的下層詞法結構。在第一次閱讀本報告時,這裡面絕大部分細節可以先忽略。

記法約定[編輯]

在顯示語法時,有下面的記法約定:

  • [pattern] 可選
  • {pattern} 0次或多次重複
  • (pattern) 分組
  • pat1 |pat2 選擇
  • pat<pat′>pat而不是pat'生成
  • fibonacci 終結符(文字)使用印表機字體

本節中的語法都是討論詞法語法。所以,空白符都顯式表示,在並列的符號之間不隱藏空白符。本節大量使用類BNF的文法,產生式的形式如下:

nonterm → alt1 | alt2 | … | altn

雖然從上下文通常可以判斷出符號的意思,但必須要注意區分元邏輯文法符號,比如|[…]和終結符(印表機字體)|和[...]的不同。

Haskell使用Unicode字符。但是,源程序現在仍傾向於使用早先版本Haskell的ASCII字符集。

Haskell語法的Unicode字符集屬性取決於Unicode委員會的定義。因此,一旦有新的Unicode發布,Haskell編譯器總是假定使用新版本。

詞法程序結構[編輯]

program → { lexeme | whitespace }
lexemeqvarid | qconid | qvarsym | qconsym | literal | special | reservedop | reservedid
literalinteger | float | char | string
special → ( | ) | , | ; | [ | ] | ` | { | }

whitespacewhitestuff {whitestuff}
whitestuffwhitechar | comment | ncomment
whitecharnewline | vertab | space | tab | uniWhite
newlinereturn linefeed | return | linefeed | formfeed
returna carriage return
linefeeda line feed
vertaba vertical tab
formfeeda form feed
spacea space
taba horizontal tab
uniWhiteany Unicode character defined as whitespace

commentdashes [ any<symbol> {any} ] newline
dashes → -- {-}
opencom → {-
closecom → -}
ncommentopencom ANY seq {ncomment ANY seq} closecom
ANY seq{ANY}<{ANY} ( opencom | closecom ) {ANY}''>
ANYgraphic | whitechar
anygraphic | space | tab

graphicsmall | large | symbol | digit | special | " | '


smallascSmall | uniSmall | _
ascSmall → a | b | … | z
uniSmallany Unicode lowercase letter

largeascLarge | uniLarge
ascLarge → A | B | … | Z
uniLargeany uppercase or titlecase Unicode letter
symbolascSymbol | uniSymbol<special | _ | " | '>


ascSymbol → ! | # | $ | % | & | ⋆ | + | . | / | < | = | > | ? | @ | \ | ^ | | | - | ~ | :
uniSymbolany Unicode symbol or punctuation
digitascDigit | uniDigit

ascDigit → 0 | 1 | … | 9
uniDigitany Unicode decimal digit
octit → 0 | 1 | … | 7

hexitdigit | A | … | F | a | … | f

詞法分析總是按照"最大滿足"規則: 每次做詞法分析,總是按照最長的詞法規則產生詞彙(lexeme類)。因此,雖然case是保留字,但cases卻不是。同樣地,雖然=是保留字,但==以及~=卻不是。

任意空格符也可看作是詞彙的分隔符。

不在ANY類的字符在Haskell程序中是非法字符,會報詞法錯誤。

注釋[編輯]

注釋是合法的空格符。

常規注釋從兩個或者多個連續橫線開始,一直到本行結束。注釋橫線前後的連續部分不能組成合法的詞。舉個例子,"-->"或者"|--"不是注釋的開始,因為他們都是合法詞彙。而"--foo"卻開始一個新的注釋。

嵌入注釋從"{-"開始,到"-}"結束。沒有合法詞彙從"{-"開始,因此,"{-"總是開始注釋。舉個例子,"{---"開始一個嵌入注釋,並不理會後面的橫線。

嵌入注釋本身不做詞法分析。第一個未匹配的字符串"-}"結束嵌入注釋。嵌入注釋可以嵌入任意多個層次。在嵌入注釋裡面出現"{-"將開啟一個新的嵌入注釋,以"-}"結束。在一個嵌入注釋內部,每個"{-"由一個對應的"-}"匹配。、

在一個常規注釋內,字符序列"{-"及"-}"沒有特殊含義。同樣,在一個嵌入注釋內,橫線序列也沒有特殊含義。

嵌入注釋也用作編譯器指示,在編譯器指示節介紹。

如果使用嵌入注釋將某些代碼注釋掉,那麼代碼中的字符串或者行注釋部分出現"{-"或"-}"將會打斷嵌入注釋。

標識符和運算符[編輯]

varid(small {small | large | digit | ' })<reservedid>
conidlarge {small | large | digit | ' }
reservedid → case | class | data | default | deriving | do | else

| foreign | if | import | in | infix | infixl
| infixr | instance | let | module | newtype | of
| then | type | where | _

一個標識符包含首字母及隨後的0個或多個字母、數字、下劃線及單引號。從詞法分析的角度,標識符分為兩類:小寫字母開頭(變量標識符)以及大寫字母開頭(構造符標識符)。標識符大小寫敏感:name, naMe以及Name是三個不同的標識符(前兩個是變量標識符,最後一個是構造符標識符)。

下劃線,"_",被處理為小寫字母,可以替代小寫字母出現。而"_"本身則是保留標示符,用作模式中的通配符。如果編譯器為未使用標識符提供告警,那麼下劃線為首字符的標識符通常建議不告警。這樣,如果某個參數foo未使用,程式設計師可以使用"_foo"做參數。

varsym( symbol<:> {symbol} )<reservedop | dashes>
consym( : {symbol})<reservedop>
reservedop → .. | : | :: | = | \ | | | <- | -> | @ | ~ | =>

如上所示,操作符由一個或多個符號字符組成。同樣,從詞法分析角度,被區分為兩類(名字空間):

  • 冒號開始的操作符是構造符
  • 其它符號開始的操作符是常規標識符

需要注意的是冒號本身被保留,唯一用作列表的構造符;這樣,它可以和其它的列表語法形式,比如"[]「及"[a,b]"統一。

除了求反是特殊的前綴形式,其它操作符都是中綴形式。此外,中綴操作符也可以用在一個分切中,以產生偏應用操作符(見分切)。所有的中綴操作符都是預定義符號,可以回彈。

本報告的其餘部分,使用6類不同的名字:
varid (變量)
conid (構造符)
tyvarvarid (類型變量)
tyconconid (類型構造符)
tyclsconid (類型類)
modid → {conid .} conid (模塊)

變量及類型變量用小寫字母開頭的標識符表示,其它類型名字用大寫字母開始的標識符表示。另外,變量和構造符有中綴形式,而其它四類名字沒有。模塊名由點分割的conid序列組成。名字空間在名字空間節討論。

在特定環境下,可以在名字前添加模塊名來限定名字。這種方法對變量、構造符、類型構造符及類型類名適用,對類型變量或模塊名無效。限定名在模塊節詳細討論。

qvarid[modid .] varid
qconid[modid .] conid
qtycon[modid .] tycon
qtycls[modid .] tycls
qvarsym[modid .] varsym
qconsym[modid .] consym

由於模塊名也是有效詞彙,限定及名字之間不允許有空格。一些詞法分析的結果可以見下表:

名字 詞法分析結果
f.g f . g (三符號)
F.g F.g (限定'g')
f.. f .. (兩符號)
F.. F.. (限定 『.』)
F. F . (兩符號)

限定不改變名字的語法處理; 比如,Prelude.+ 是中綴操作符,和Prelude中定義的"+"(嵌套聲明)有相同的綴形式。

數符[編輯]

decimaldigit{digit}
octaloctit{octit}
hexadecimalhexit{hexit}
integerdecimal

| 0ooctal | 0Ooctal
| 0xhexadecimal | 0Xhexadecimal

floatdecimal.decimal [exponent]

| decimal exponent

exponent(e | E) [+ | -] decimal

Haskell有兩種不同的數符: 整數符和浮點數符。整數符可以用十進位(預設)、八進位(Oo或OO前綴)或者十六進位(Ox或OX)表示。浮點數符總是用十進位表示。浮點數符在點號前後都必須包含數字,以保證和其它帶點的字符區別開來。負數符在操作符應用節討論。數符的類型在節討論。

字符和串符[編輯]

char → ' (graphic<' | \> | space | escape<\&>) '
string → " {graphic<" | \> | space | escape | gap} "
escape → \ ( charesc | ascii | decimal | o octal | x hexadecimal )
charesc → a | b | f | n | r | t | v | \ | " | ' | &
ascii → ^cntrl | NUL | SOH | STX | ETX | EOT | ENQ | ACK

| BEL | BS | HT | LF | VT | FF | CR | SO | SI | DLE
| DC1 | DC2 | DC3 | DC4 | NAK | SYN | ETB | CAN
| EM | SUB | ESC | FS | GS | RS | US | SP | DEL

cntrlascLarge | @ | [ | \ | ] | ^ | _
gap → \ whitechar {whitechar} \

字符在兩個單引號之間,比如'a'。串符在兩個雙引號之間,比如"Hello"。

字符和串符中可以使用退出碼(escape)來表示特殊字。注意,單引號'可以在出現在串中,但必須使用退出碼;同樣地,雙引號"也可以出現在字符中,也必須使用退出碼。\後面跟字符表示退出碼。上面詞法規則中,charesc類包含了可移植的特殊字符,比如「報警"(\a),"退格"(\b), "換頁"(\f),"新行"(\n),"回車"(\r),"水平制表"(\t)以及"垂直制表"(\v)。

Haskell提供Unicode字符集的退出碼,包括像\^X這樣的控制符。數字退出碼,比如\137,表示該字符的數字表示是137;Haskell也允許八進位和十六進位的數字退出碼。

根據「最大匹配」原則,\後面的數字退出碼總是包含最多的連續數字,且長度任意。同樣地,\後面的ASCII退出碼也滿足這條規則。比如,"\SOH"詞法分析結果是長度為1的串符。退出碼\&表示"空字符",這樣,我們可以構造類似"\137\&9"或"\SO\&H"這樣的串符(串的長度都為2)。"\&"等價於"",而字符'\&'卻是不允許的。字符之間的等價性在標準Haskell類型節定義。

兩個反斜槓之間只有一個或多個空白符,稱為"間隔「。串內如果有間隔,則間隔被忽略。這樣,我們可以寫出超過一行的字符串:在行的末尾插入反斜槓,在下一行的開頭插入斜槓。比如:

"Here is a backslant \\ as well as \137, \  
    \a numeric escape character, and \^X, a control character."

串符實際上字符列表的簡寫(見列表節)。

排版[編輯]

Haskell允許某些文法省略大括號和分號,取而代之,使用排版來傳遞相同信息。這樣,我們可以同時使用排版和不排版兩種編程風格,即使在同一程序內部,這兩種風格也可以混用。由於程序排版不是必選項,haskell程序可直接由其它程序生成。

排版的haskell程序,使用排版格式的地方完全可以通過添加大括號和分號的辦法實現等價的Haskell程序。這個等價的Haskell程序即不排版程序。

大致上,大括號和分號可用下面的方法添加:如果關鍵字where、let、do、of後面的左括號被省略,則表示開始一個排版("越位")列表。這時,下一個詞彙(不管是不是在新行)的縮進被記錄下來(詞彙前面的空白符可能也包含注釋),插入被省略的左括號。在處理後續行時,如果後續行包含更多的縮進,表示繼續前面行的語法項(不插入);如果後續行包含相同的縮進,表示開始一個新的語法項,插入一個分號;如果後續行縮進變少,則表示當前排版列表結束(插入右括號)。如果where、let、do、of後面緊跟的非括號詞彙,其縮進比當前縮進級別相等或者更小,這時,Haskell並不開始新的排版列表,而是插入括號對"{}",仍然在當前縮進上進行排版處理(即,插入一個分號或者右括號)。如果包含當前排版列表的文法類結束,也將插入右括號;即,如果某處出現詞彙錯誤,而將此錯誤詞彙替換為右括號時正確,則在此處添加右括號。排版規則只對自己插入的左括號配對;顯式的左括號必須有顯式的右括號配對。如果給出顯式左括號,則括號內不對括號外的結構排版,即便某行的縮進在之前某個插入的隱式左括號的左邊。

版式節對排版規則給出更精確的定義。

給出排版規則後,一個新行可能結束多個排版列表。同樣地,規則使得:

f x = let a = 1; b = 2  
          g y = exp2  
       in exp1

a, b 和 g 從屬於一個排版列表。

一個實例,下面是一個排版程序(有些人為修飾):

module AStack( Stack, push, pop, top, size ) where  
data Stack a = Empty  
             | MkStack a (Stack a)  
 
push :: a -> Stack a -> Stack a  
push x s = MkStack x s  
 
size :: Stack a -> Int  
size s = length (stkToLst s)  where  
           stkToLst  Empty         = []  
           stkToLst (MkStack x s)  = x:xs where xs = stkToLst s  
 
pop :: Stack a -> (a, Stack a)  
pop (MkStack x s)  
  = (x, case s of r -> i r where i x = x) -- (pop Empty) is an error  
 
top :: Stack a -> a  
top (MkStack x s) = x                     -- (top Empty) is an error

程序應用排版規則後的結果如下:

module AStack( Stack, push, pop, top, size ) where  
{data Stack a = Empty  
             | MkStack a (Stack a)  
 
;push :: a -> Stack a -> Stack a  
;push x s = MkStack x s  
 
;size :: Stack a -> Int  
;size s = length (stkToLst s)  where  
           {stkToLst  Empty         = []  
           ;stkToLst (MkStack x s)  = x:xs where {xs = stkToLst s  
 
}};pop :: Stack a -> (a, Stack a)  
;pop (MkStack x s)  
  = (x, case s of {r -> i r where {i x = x}}) -- (pop Empty) is an error  
 
;top :: Stack a -> a  
;top (MkStack x s) = x                        -- (top Empty) is an error  
}

特別要注意的是:

  • 以}};pop開始的行,終結前面行的排版應用了3次排版規則,對應3層嵌套where語句。
  • 在case表達式和元組之間where語句後又添加了一個右括號,因為系統檢測到元組的結束,意味著語法case項也要結束。
  • 添加最後一個右括號是因為,最後一列是對end-of-file符號的0級縮進。

表達式[編輯]

本章,我們討論Haskell表達式的語法以及非形式語義,我們也適時給出表達式到Haskell核的轉化。除了let表達式,所有的轉化都保持程序的動態及靜態語義。轉化中用到的自由變量或者構造符都是來自於Prelude的實體。比如,在列表組合轉換中用到的"concatMap"指的就是Prelude中定義的concatMap, 無論符號"concatMap"是否出現在列表組合作用域或是它所綁定的作用域(如果concatMap在列表組合的作用域)。

expinfixexp :: [context =>] type (表達式類型簽名)

| infixexp

infixexplexp qop infixexp (中綴操作符應用)

| - infixexp (前綴求反)
| lexp

lexp → \ apat1apatn -> exp (lambda抽象, n ≥ 1)

| let decls in exp (let表達式)
| if exp [;] then exp [;] else exp (條件式)
| case exp of { alts } (case表達式)
| do { stmts } (do表達式)
| fexp

fexp → [fexp] aexp (函數應用) aexpqvar (變量)

| gcon (通用構造符)
| literal
| ( exp ) (括號表達式)
| ( exp1 , … , expk ) (元祖, k ≥ 2)
| [ exp1 , … , expk ] (列表, k ≥ 1)
| [ exp1[, exp2] ... [exp3] ] (算術序列)
| [ exp | qual1 , … , qualn ] (列表組合, n ≥ 1)
| ( infixexp qop ) (左分切)
| ( qop<-> infixexp ) (右分切)
| qcon { fbind1 , … , fbindn } (標記構造, n ≥ 0)
| aexp<qcon>{ fbind1 , … , fbindn } (標記更新, n ≥ 1)

表達式,包括中綴表達式,用操作符結合信息來分辨(嵌套聲明節)。連續的無括號操作符,如果優先級相等,必須指定是左結合還是右結合,否則會產生語法錯誤。假定無括號表達式"x gop(a,i) y qop(b,j) z"(qop(a,i)表示操作符綴為a,優先級是i),且i=j,這時必須在"x qop(a,i) y"或者"y qop(b,j) z"兩側添加括號,除非a=b=l或a=b=r。

結合解析節給出了包括中綴表達式在內的表達式結合判定算法。

求反是唯一的前綴表達式;它和Prelude中(嵌套聲明)的中綴操作符-有相同的優先級。

如果考慮lambda抽象、let表達式和條件式的延伸,則通常會出現歧義文法。歧義文法的判定,由下面的元規則確定:這些結構總是儘可能的右延。

下面是一些樣例解析:

表達式 解析
f x + g y (f x) + (g y)
- f x + y (- (f x)) + y
let { ... } in x + y z + (let { ... } in (x + y))
f x y ::Int (f x y)::Int
\ x -> a+b :: Int \ x -> ((a+b) :: Int)

為了便於闡述,本節餘下部分假定包含中綴操作符在內的表達式,根據操作符的結合信息有唯一的解析。

錯誤[編輯]

Haskell中,表達式求值過程中的錯誤,用⊥(底)表示。Haskell不區分⊥和程序非終止。Haskell是一個非即時語言,所有的Haskell類型都包含⊥。即,任何類型的值都可能綁定到某個計算,計算啟動後返回一個錯誤。求值過程中出現錯誤,會立即終止程序,不能被用戶捕獲。Prelude提供兩個函數,可以直接產生錯誤:

error     :: String -> a  
undefined :: a

調用error終止當前程序執行,並返回合適錯誤信息給作業系統。同時,會以系統相關的方式顯式錯誤串。調用undefined時,編譯器產生錯誤信息。

Haskell表達式的轉化使用error和undefined顯式表達可能發生執行期錯誤的位置。發生錯誤時的程序行為由具體實現決定。表達式轉化傳遞給error函數的消息僅僅只是建議;實際上,由具體實現來列印錯誤發生時的大概信息。

變量,構造符,操作符和終結符[編輯]

aexpqvar (變量)

| gcon (一般構造符)
| literal

gcon → ()

| []
| (,{,})
| qcon

varvarid | ( varsym ) (變量)
qvarqvarid | ( qvarsym ) (限定變量)
conconid | ( consym ) (構造符)
qconqconid | ( gconsym ) (限定構造符)
varopvarsym | ` varid ` (變量操作符)
qvaropqvarsym | ` qvarid ` (限定變量操作符)
conopconsym | ` conid ` (構造符操作符)
qconopgconsym | ` qconid ` (限定構造符操作符)
opvarop | conop (操作符)
qopqvarop | qconop (限定操作符)
gconsym → : | qconsym

Haskell通過特殊語法支持中綴表示。操作符可以看做是使用中綴表示的函數(操作符應用),也可以通過分切(分切)實現偏應用。

一個操作符可以是一個符號,比如+或$$,也可以是一個方言標記(反引號)的常規符號,比如`op`. 舉個例子,前綴應用op x y,也可以寫成中綴應用x `op` y。如果沒有`op`沒有結合定義,那麼它預設有最高的優先級並左結合(見聲明與綁定節)。、

同樣,符號表示的操作符也可以轉換為常規符號,方法是添加括號。比如,(+) x y和x + y等價。foldr (*) 1 xs 和foldr (\x y -> x * y) 1 xs等價。

命名一些內置類型的構造符時使用了一些特殊語法,比如產生式gcon和literal。這些將在標準Haskell類型節闡述。

一個整數符表示函數fromInteger應用於類型為Integer的對應值。類似地,一個浮點數符表示fromRational應用一個類型為Rational的值(即,比率數(有理數))。

轉化:整數符i被轉化為fromInteger i,這裡fromInteger是Num類的一個方法(見節)。

浮點數符f等價於fromRational (n Ratio.% d),這裡fromRational是Fractional類的方法,Ratio.% 由兩個整數構造一個有理數,在Ratio庫中定義。整數n和d選擇方法是n/d = f.

Curry應用及Lambda抽象[編輯]

fexp[fexp] aexp (函數應用)
lexp → \ apat1 … apatn -> exp (lambda抽象, n ≥ 1)

函數應用記為e1 e2。函數應用是左結合,因此(f x) y中的括號可以省略。除函數外,e1也可以是數據構造符,因此,數據構造符的偏應用也是允許的。

Lambda抽象記為,\ p1 ... pn -> e, 其中,pi是模式。形如\x:xs -> x的表達式語法上非法; 合法寫法是\ (x:xs) -> x。

模式集合必須是線性的-集合中的變量至多出現一次。

轉化:下面的轉化等價:

\ p1 … pn -> e = \ x1 … xn -> case (x1, …, xn) of (p1, …, pn) -> e
其中,xi是新標識符。

如果由這裡的轉換以及模式匹配節給出的case表達式以及模式匹配的語義不能匹配某個模式,則其結果是⊥。

操作符應用[編輯]

infixexplexp qop infixexp

| - infixexp (前綴求反)
| lexp

qopqvarop | qconop (限定操作符)
e1 qop e2是二元操作符qop應用於表達式e1及e2的中綴形式。 表達式-e形式特殊,是Haskell中唯一的前綴操作符,是negate (e)的語法表示。二元操作符"-"不一定指 Prelude中定義的"-"操作符,它有可能被模塊系統重新定義。而,單元操作符"-"總是指Prelude中定義的求反函數。本地定義"-"操作符和單元求反之間沒有聯繫。

前綴求反和中綴操作符-有相同的優先級(見嵌套聲明節表)。表達式e1 - e2被解析為二元操作符-的中綴應用,因此,如果要得到另外一種解析,我們必須寫成e1 (-e2)。類似地,(-)是(\ x y -> x - y)的語法,和其它中綴操作符一樣,它不表示(\ x -> -x),我們必須使用求反來獲得(\ x-> -x)。

轉化:下面式子等價:

e1 = (op) e1 e2
-e = negate (e)

分切[編輯]

aexp → ( infixexp qop ) (左分切)

| ( qop<-> infixexp ) (右分切)

分切被記為( op e)或(e op),這裡,op是二元操作符,e是表達式。分切是二元操作符實現偏應用的便利語法。

語法優先級規則應用於分切如下所述:若且唯若(x op (e))和(x op e)語法解析相同時,(op e)合法。同理,(e op)也有此規則。舉個例子,(*a+b)語法非法,但(+a*b)及(*(a+b))卻合法。由於(+)左結合,(a+b+)語法正確,但(+a+b)錯誤。後者可以合法記為(+(a+b))。另外一個例子,由表達式節let/lambda元規則,表達式:

  (let n = 10 in n +)

非法。因為,表達式

  (let n = 10 in n + x)

語法解析為:

  (let n = 10 in (n + x))

而不是

  ((let n = 10 in n) + x)

由於語法中對-處理特殊,(- exp)不是分切,而是前綴求反應用。這在前面章節中有所論述。然而,Prelude定義了一個subtract函數,(subtract exp)等價於前面被禁掉的分切。同樣地,表達式(+ (- exp))也能達到相同的目的。

轉化:下面式子等價:

(op e) = \ x -> x op e
(e op) = \ x -> e op x
其中,op是二元操作符,e是表達式,x是不在e中自由出現的變量。

條件式[編輯]

lexp → if exp [;] then exp [;] else exp

條件式形如if e1 then e2 else e3。當e1值為True時,返回e2的值,當e1值為False時返回e3,其它情況下,返回⊥

轉化:下面式子等價:

if e1 then e2 else e3 = case e1 of { True -> e2 ; False -> e3 } 其中,True和False是Prelude中定義的、類型為Bool的非空構造符。e1的類型必須為Bool; e2和e3類型必須相同,且都是整個條件式的類型。

列表[編輯]

元組[編輯]

元表達式和括號表達式[編輯]

算術序列[編輯]

列表組合[編輯]

let表達式[編輯]

case表達式[編輯]

do表達式[編輯]

域標記數據類型[編輯]

表達式類型簽名[編輯]

模式匹配[編輯]

聲明和綁定[編輯]

類型和類概述[編輯]

用戶定義數據類型[編輯]

類型類及重載[編輯]

嵌套聲明[編輯]

函數及模式綁定的靜態語義[編輯]

kind推導[編輯]

模塊[編輯]

模塊結構[編輯]

導出結構[編輯]

導入聲明[編輯]

導入和導出實例聲明[編輯]

命名衝突和閉包[編輯]

標準prelude[編輯]

分開編譯[編輯]

抽象數據類型[編輯]

預定義類型和類[編輯]

標準Haskell類型[編輯]

即時求值[編輯]

標準Haskell類[編輯]

[編輯]

基本輸入/輸出[編輯]

標準I/O函數[編輯]

序列化I/O操作[編輯]

I/O單子異常處理[編輯]

外部函數接口[編輯]

外部語言[編輯]

上下文[編輯]

詞法結構[編輯]

外部聲明[編輯]

外部實體規範[編輯]

列集[編輯]

外部C接口[編輯]

標準prelude[編輯]

Prelude preludelist[編輯]

Prelude preludeText[編輯]

Prelude preludeIO[編輯]

語法參考[編輯]

記法約定[編輯]

詞法語法[編輯]

版式[編輯]

文字注釋[編輯]

上下文無關語法[編輯]

結合解析[編輯]

派生實例規範[編輯]

Eq及Ord的派生實例[編輯]

Enum的派生實例[編輯]

Bounded的派生實例[編輯]

Read和Show的派生實例[編輯]

一個例子[編輯]

編譯器指示[編輯]

內聯[編輯]

特例化[編輯]

語言擴展[編輯]

Haskell 2010[編輯]

Control.Monad[編輯]

函子及單子類[編輯]

函數[編輯]

Data.Array[編輯]

不可變的非即時數組[編輯]

數組構造[編輯]

訪問數組[編輯]

增量式數組更新[編輯]

派生數組[編輯]

規範[編輯]

Data.Bits[編輯]

Data.Char[編輯]

字符和串[編輯]

字符分類[編輯]

大小寫轉換[編輯]

單數字符[編輯]

數值表示[編輯]

串表示[編輯]

Data.Complex[編輯]

矩陣式[編輯]

極坐標式[編輯]

共軛[編輯]

規範[編輯]

Data.Int[編輯]

符號整數類型[編輯]

Data.Ix[編輯]

Ix類[編輯]

Ix的派生實例[編輯]

Data.List[編輯]

基本函數[編輯]

列錶轉換[編輯]

化簡列表(摺疊)[編輯]

構建列表[編輯]

子列表[編輯]

搜索列表[編輯]

索引列表[編輯]

列表的配對和拆對[編輯]

特殊列表[編輯]

泛化函數[編輯]

Data.Maybe[編輯]

Maybe類型和操作[編輯]

規範[編輯]

Data.Ratio[編輯]

規範[編輯]

Data.Word[編輯]

無符號整數類型[編輯]

Foreign[編輯]

Foreign.C[編輯]

Foreign.C.Error[編輯]

errno值的Haskell表示[編輯]

Foreign.C.String[編輯]

C串[編輯]

C的寬字符串[編輯]

Foreign.C.Types[編輯]

C類型的表示[編輯]

Foreign.ForeignPtr[編輯]

固化數據指針[編輯]

Foreign.Marshal[編輯]

Foreign.Marshal.Alloc[編輯]

內存分配[編輯]

Foreign.Marshal.Array[編輯]

數組列集[編輯]

Foreign.Marshal.Error[編輯]

Foreign.Marshal.Utils[編輯]

通用列集工具[編輯]

Foreign.Ptr[編輯]

數據指針[編輯]

函數指針[編輯]

整數類型與指針間無損轉換[編輯]

Foreign.StablePtr[編輯]

Haskell值的穩定引用[編輯]

Foreign.Storable[編輯]

Numeric[編輯]

顯示[編輯]

讀取[編輯]

其它[編輯]

System.Environment[編輯]

System.Exit[編輯]

System.IO[編輯]

IO單子[編輯]

文件和句柄[編輯]

打開與關閉文件[編輯]

句柄操作[編輯]

文本輸入和輸出[編輯]

System.IO.Error[編輯]

I/O錯誤[編輯]

I/O錯誤類型[編輯]

拋出及捕獲I/O錯誤[編輯]

參考書目[編輯]

術語(中英文對照)[編輯]

  structure: 结构
  lexical: 词法的
  expression: 表达式
  declaration: 声明
  binding: 绑定
  module: 模块
  type:  类型
  class: 类
  input: 输入
  output: 输出
  Foreign Function Interface: 外部函数接口
  syntax: 语法
  derived: 派生
  Instance: 实例
  compiler: 编译器
  pragmas: 指示
  specification: 规范
  namespace:  名字空间
  kernel:  核
  convention: 约定
  literal: 终结符 文字
  layout:  版式 排版 
  constructor: 构造符
  Application:  应用
  abstraction: 抽象
  operator: 操作符 
  section: 分切(expression section)
  conditional: 条件式
  list: 列表
  tuple: 元组
  unit expression: 元表达式
  parenthesized expression: 括号表达式
  sequence: 序列
  comprehension: 组合
  field: 域
  label: 标记
  pattern: 模式
  matching: 匹配
  type signature: 类型签名 
  overview: 概述
  datatype: 数据类型
  alert: 报警
  backspace: 退格
  form feed: 换页
  new line: 新行
  carriage return: 回车
  horizontal tab: 水平制表
  vertical tab: 垂直制表
  escape code: 退出码
  backslant: 反斜杠
  gap: 间隔   
  overload: 重载
  import: 导入
  export: 导出
  closure: 闭包
  strict evaluation: 即时求值
  monad: 单子
  exception: 异常
  marshal: 列集
  fixity: 缀 结合 (infix[中缀], leftfix[左结合], rightfix[右结合])
  resolution: 解析
  inline: 内联
  specialization: 特例化
  extension: 扩展
  functor: 函子
  Immutable: 不可变对象
  non-strict: 非即时
  rectangular form: 矩阵式
  polar form: 极坐标式
  conjugate: 共轭
  zip: 配对
  unzip: 拆对
  finalise: 固化
  handle: 句柄 
  throw: 抛出
  catch: 捕获
  variant: 变体
  workshop: 工作组会议
  calculus: 演算
  polymorphic: 多型
  syntactic sugar: 语法糖
  denotational semantics: 指称语义
  ad-hoc: 临时
  scope: 作用域
  literate programming: 作文式编程
  wild card: 通配符 
  identifier: 标识符 
  negation: 求反
  partially appied: 偏应用 
  rebound: 回弹 
  qualified: 限定
  brace: 大括号
  open brace: 左括号
  close brace: 右括号
  lexeme: 词汇
  production: 产生式
  extent: 延伸( extent of let expression: let 表达式的延伸)
  error: 错误
  non-termination: 非终止
  guard: 守护