Haskell

纯函数式编程语言

Haskell

Haskell(发音为/ˈhæskəl/)是一种标准化的,通用的纯函数编程语言,有非限定性语义和强静态类型。它的命名源自美国逻辑学家哈斯凯尔·加里,他在数理逻辑方面上的工作使得函数式编程语言有了广泛的基础。在Haskell中,“函数是第一类对象”。作为一门函数编程语言,主要控制结构是函数。Haskell语言是1990年在编程语言Miranda的基础上标准化的,并且以λ演算为基础发展而来。这也是为什么Haskell语言以希腊字母“λ”(Lambda)作为自己的标志。Haskell具有“证明即程序、命题为类型”的特征。1985年,Miranda发行后,惰性函数式语言的关注度增长。到1987年前,出现了十多种非限定性、纯函数式语言。其中,Miranda使用得最为广泛,但还没有在公共领域出现。在美国波特兰州俄勒冈的函数式编程语言与计算机结构大会(FPCA '87)上,与会者一致同意组成一个委员会,为这样的语言定义一种开放性标准。该委员会旨在整合已有的函数式语言,为将来的函数式语言设计研究工作奠定基础。

Haskell(发音为/ˈhæskəl/)是一种标准化的,通用的纯函数编程语言,有非限定性语义和强静态类型。它的命名源自美国逻辑学家哈斯凯尔·加里,他在数理逻辑方面上的工作使得函数式编程语言有了广泛的基础。在Haskell中,“函数是第一类对象”。作为一门函数编程语言,主要控制结构是函数。Haskell语言是1990年在编程语言Miranda的基础上标准化的,并且以λ演算为基础发展而来。这也是为什么Haskell语言以希腊字母“λ”(Lambda)作为自己的标志。Haskell具有“证明即程序、命题为类型”的特征。

发展

1985年,Miranda发行后,惰性函数式语言的关注度增长。到1987年前,出现了十多种非限定性、纯函数式语言。其中,Miranda使用得最为广泛,但还没有在公共领域出现。在美国波特兰州俄勒冈的函数式编程语言与计算机结构大会(FPCA '87)上,与会者一致同意组成一个委员会,为这样的语言定义一种开放性标准。该委员会旨在整合已有的函数式语言,为将来的函数式语言设计研究工作奠定基础。

Haskell 1.0到1.4

1990年定义了Haskell的第一个版本(“Haskell 1.0”)。委员会形成了一系列的语言定义(1.0,1.1,1.2,1.3,1.4)。

Haskell 98

1997年底,该系列形成了Haskell 98,旨在定义一个稳定、最小化、可移植的语言版本以及相应的标准库,以用于教学和作为将来扩展的基础。委员会明确欢迎创建各种增加或集成实验性特性的Haskell 98的扩展和变种。

1999年2月,Haskell 98语言标准公布,名为《The Haskell 98 Report》。2003年1月,《Haskell 98 Language and Libraries: The Revised Report》公布。接着,Glasgow Haskell Compiler(GHC)实现了当时的de facto标准,Haskell快速发展。

Haskell Prime

2006年早期,开始了定义Haskell 98标准后续的进程,非正式命名为Haskell Prime。这是个修订语言定义的不断增补的过程,每年产生一个新的修订版。第一个修订版于2009年11月完成、2010年7月发布,称作Haskell 2010。

Haskell 2010

Haskell 2010加入了外部函数接口(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。

特性

Haskell是现有的一门开放的、已发布标准的,且有多种实现的语言。支持惰性求值、模式匹配、列表解析、类型类和类型多态。它是一门纯函数编程语言,这意味着大体上,Haskell中的函数没有副作用。Haskell用特定的类型来表达副作用,该类型与函数类型相互独立。纯函数可以操作并返回可执行的副作用的类型,但不能够执行它们,只有用于表达副作用的类型才能执行这些副作用,Haskell以此表达其它语言中的非纯函数。

Haskell拥有一个基于Hindley-Milner类型推论的静态、强类型系统。Haskell在此领域的主要创新就是加入了类型类(type class),原本设想作为重载的主要方式,在之后发现了更多用途。

Haskell的主要实现GHC是个解释器,也是个原生代码编译器。它可以在大多数平台运行,GHC在并发和并行上具有高性能的实现能力,也有丰富的类型系统,如广义代数数据类型和类型族(Type Families)。

单子是一个抽象类型,可以表达不同种类的计算,包括异常处理、非确定性、语法分析以及软件事务内存,其中一个应用是用于表达副作用的类型。单子定义为普通的数据类型,同时Haskell也为其提供了几种语法糖。

Haskell有一个活跃的社区,在线上包仓库Hackage上有丰富的第三方开源库或工具。

语法

数据类型

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 产生一个元素的无限重复的Listcycle 接受一个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表示逻辑非。

基本的 Typeclass:

Eq 可判断相等性的typeOrd 可比较大小的typeShow 可表示为字符串的typeRead 可从字符串转换出值的typeEnum 连续的,也就是可枚举的type。每个值都有后继 (successer) 和前置 (predecesor),分别可以通过 succ 函数和 pred 函数得到。Bounded 有上限和下限。例如:maxBound:: Char 或者 maxBound:: BoolNum 数字IntegralFloating

表达式

let表达式:格式为 let [bindings] in [expressions] 。let 也可以定义局部函数 。在一行中设定多个名字的值,可用分号隔开。List Comprehension 中 let绑定的名字在输出函数及限制条件中都可见;忽略了 let 绑定的 in 部分,因为名字的可见性已经预先定义好了。if then else是表达式Case表达式:

 case expression of pattern -> resultpattern -> resultpattern -> result...  

控制结构

if then else是分段函数定义时的语法糖。与C语言不同,要求必须有else部分。类似于C语言分支语句的情形,叫做pattern matching,例子如下:

1pts :: Int -> Int  2-3pts 1 = 10 4-5pts 2 = 6 6-7pts x8-9| x <= 6 = 7 - x10-11| otherwise = 012-13(||) :: Bool -> Bool -> Bool  -- 或操作的类型与定义14-15True  || _ = True-- 第二个参数是任何值都匹配。16-17False || y = y

函数

函数调用有最高运算顺序,例如succ 9*10表示(succ 9)*10。

函数的调用使用空格符而不是括号。

函数的复合调用是左结合

首字母大写的函数是不允许的

两个参数的函数的调用可以写成中缀形式: param1 `funcName` param2

运算符可以用括号围起来,作为前缀形式:(+) 2 3 的结果为5

在 ghci 下,我们可以使用 ``let`` 关键字来定义一个常量。在 ghci 下执行 ``let a=1`` 与在脚本中编写 ``a=1``等价的

11 --funcName arguments = expression  --定义函数的一般形式 22 area r = pi * r ^ 2 -- 定义了一个函数 33 area 101 -- 调用了函数 44 f1 f2 3.14 -- 函数调用是左结合,等效于(f1 f2) 3.14 5-66 --模式匹配方式定义 77 factorial :: (Integral a) => a -> a88 factorial 0 = 199 factorial n = n * factorial (n - 1)10-1111 as模式,是将一个名字和 @ 置于模式前,可以在按模式分割参数值时仍保留对其整体的引用。如nameGlobal@(x:y:ys),nameGlobal会匹配出与 x:y:ys 对应的东西。as模式在较大的模式中保留对整体的引用,从而减少重复性的工作。12-1313 heron a b c = sqrt (s * (s - a) * (s - b) * (s - c))1414where-- where在表达式中局部绑定了名字s与一个值。也可以在表达式之前用let ... in语法1515s = (a + b + c) / 216-1717 absolute x -- 绝对值函数,使用了分段函数语法糖(称作Guards)1818| x < 0= 0 - x19 19| otherwise = x -- 兜底条件20-2121 bmiTell :: (RealFloat a) => a -> a -> String  2222 bmiTell weight height  2323| bmi <= 18.5 = "You're underweight."  2424| bmi <= 25.0 = "You're normal. "  2525| bmi <= 30.0 = "You're fat."  2626| otherwise= "You're overweight."  2727where bmi = weight / height ^ 2  -- 使用where定义多个名字来避免重复28-2929 --where也可以用模式匹配3030 initials :: String -> String -> String  3131 initials firstname lastname = [f] ++ ". " ++ [l] ++ "."  3232where (f:_) = firstname  3333 (l:_) = lastname  34-3535 --where可以定义函数。在定义一个函数的时候也写几个辅助函数摆在 where 绑定中。 而每个辅助函数也可以透过 where 拥有各自的辅助函数。3636 calcBmis :: (RealFloat a) => [(a, a)] -> [a]  3737 calcBmis xs = [bmi w h | (w, h) <- xs] 3838where bmi weight height = weight / height ^ 2  39-4040 funcName :: type1 -> type2 -> type3 -- 其中,::表示类型特征(type signature),->是右结合,这里等效于type1 -> (type2->type3),给定一个type1的输入参数,返回一个函数(type2->type3)41-4242 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] -> asum' 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)

异常处理

提供了处理异常的函数Template:Haskell/Template:Haskell/Template:Haskell/Template:Haskell.

1import Prelude hiding(catch) 22 import Control.Exception 33 instance Exception Int 44 instance Exception Double 55 main = do 66catch 77(catch 88  (throw (42::Int)) 99  (\e-> print (0,e::Double)))1010(\e-> print (1,e::Int))

输出结果

(1,42)

类似于 C++

11#include <iostream> 22 using namespace std; 33 int main() { 44try { 55 throw (int)42; 66} catch (double e) { 77 cout << "(0," << e << ")" << endl; 88} catch (int e) { 99 cout << "(1," << e << ")" << endl;1010}1111 }

另外一个例子:

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

如果仅有一个错误条件,Template:Haskell类足够用了,确省是Haskell的Template:Haskellclass. 更复杂的出错处理用Template:Haskell或Template:Haskellmonads, 类似功能可用Template:Haskell。

语言

以Haskell为基础的衍生语言有很多种,它们分别是:并行Haskell,扩充Haskell(旧名Goffin),Eager Haskell, Eden, DNA-Hakell 和面向对象的变体(Haskell++, O'Haskell, Mondrian)。另外Haskell还被作为其他语言设计新功能时的样板,例如Python中的Lambda标记语句。

广告位:底部(ad-bottom)—— 请到中台「公共区块」编辑此内容