热门问题
时间线
聊天
视角
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年前,出现了十多种非限定性、纯函数式语言。其中,Miranda使用的最为广泛,但还没有出现在公共领域。在俄勒冈波特兰的函数式编程语言与计算机结构大会(FPCA '87)上,参加者一致同意形成一个委员会来为这样的语言定义一种开源标准。该委员会旨在整合已有函数式语言,作为将来的函数式语言设计研究工作的基础。[33]
能够确使类型安全的运算符重载的类型类,是由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)。

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快速发展。
2006年早期,开始了定义Haskell 98标准后续的进程,非正式命名为Haskell Prime。[38]这是个修订语言定义的不断增补的过程,每年产生一个新的修订版。第一个修订版于2009年11月完成、2010年7月发布,称作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。
Remove ads
特性
Haskell是现有的一门开放的、已发布标准的,且有多种实现的语言。[37]支持惰性求值、模式匹配、列表解析、类型类和类型多态。它是一门纯函数式编程语言,这意味着大体上,Haskell中的函数没有副作用。Haskell用特定的类型来表达副作用,该类型与函数类型相互独立。纯函数可以操作并返回可执行的副作用的类型,但不能够执行它们,只有用于表达副作用的类型才能执行这些副作用,Haskell以此表达其它语言中的非纯函数。
Haskell拥有一个基于Hindley-Milner类型推论的静态、强类型系统。Haskell在此领域的主要创新就是加入了类型类,原本设想作为重载的主要方式,[39]在之后发现了更多用途。[40]
Haskell的主要实现GHC是个解释器,也是个原生代码编译器。它可以在大多数平台运行,GHC在并发和并行上具有高性能的实现能力,[41]也有丰富的类型系统,如广义代数数据类型和类型家族)。
单子是一个抽象類型,可以表达不同种类的计算,包括异常处理、非确定性、语法分析以及软件事务内存,其中一个应用是用于表达副作用的类型。单子定义为普通的数据类型,同时Haskell也为其提供了几种语法糖。
Haskell有一个活跃的社区,在线上包仓库Hackage上有豐富的第三方开源库或工具。[42]
Remove ads
语法
Haskell是强类型语言。Char的字面值用单引号围起;字符串即[Char]类型,其字面值用双引号括起来。Int通常为32位整型;Integer是无界整型;Float 表示单精度的浮点数;Double 表示双精度的浮点数;Bool 只有两种值:True 和 False。
使用[ ]与逗号分隔符,定义一个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 被禁止。
- 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函数,否则它会占据整个右边部分。
例如:(\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)
提供了处理异常的函数try
、catch
、finally
/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. 更复杂的出错处理用Error
或ErrorT
monads, 类似功能可用`catch`
。
示例
如下是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保证第三行不会执行负参数。
"Prelude"是一个类似C中标准库的小函数集合。使用Prelude,并用无指定参数的写法,它可以改成:
fac = product . enumFromTo 1
上面的定义接近于数学中的定义:f = g o h(参见复合函数),这并不是一个对变量赋值的语句。
Haskell中可以定义高阶函数(Higher-order Function),既将函数作为一个参数来使用,也可以将函数作为结果输出,例如:
f :: (Int -> Int) ->(Int -> Int)
f g = \x -> g x + 5
这里f就是一个高阶函数,它取一个从Int到Int的函数g作为参数,输出一个从Int到Int的函数。高阶函数的使用在一些情况下将极大的简化代码。
Haskell的编译器
参考文献
外部連結
Wikiwand - on
Seamless Wikipedia browsing. On steroids.
Remove ads