Loading AI tools
函數式編程語言 来自维基百科,自由的百科全书
Haskell(发音为/ˈhæskəl/)[26]是一种标准化的,通用的纯函数式编程语言,有惰性求值和强静态类型[27]。它的命名源自美国逻辑学家哈斯凯尔·加里,他在数理逻辑方面上的工作使得函数式编程语言有了广泛的基础。在Haskell中,“函数是头等物件”[28]。作为一门函数编程语言,主要控制结构是函数。Haskell语言是1990年在编程语言Miranda语言的基础上标准化的,并且以λ演算为基础发展而来。这也是为什么Haskell语言以希腊字母“λ”(Lambda)作为自己的标志。Haskell具有“证明即程序、命题为类型”的特征[29][30][31][32]。
![]() | |
编程范型 | 纯函数式 |
---|---|
设计者 | Lennart Augustsson, Dave Barton, Brian Boutel, Warren Burton, Joseph Fasel, Kevin Hammond, Ralf Hinze, 保罗·胡达客, John Hughes, Thomas Johnsson, Mark Jones, 西蒙·佩顿·琼斯, John Launchbury, Erik Meijer, John Peterson, Alastair Reid, Colin Runciman, 菲利普·瓦德勒 |
发行时间 | 1990年[1] |
当前版本 |
|
类型系统 | 推论, 静态, 强类型 |
操作系统 | 跨平台 |
文件扩展名 | .hs , .lhs |
网站 | www |
主要实现产品 | |
GHC, Hugs, NHC, JHC, Yhc | |
启发语言 | |
Clean,[3] FP,[3] Gofer,[3] Hope和Hope+,[3] Id,[3] ISWIM,[3] KRC,[3] Lisp,[3] Miranda,[3] ML和Standard ML,[3] Orwell, SASL,[3] Scheme,[3] SISAL[3] | |
影响语言 | |
Agda,[4] Bluespec,[5] C++11/Concepts,[6] C#/LINQ,[7][8][9][10] Cayenne,[7] Clean,[7] Clojure,[11] CoffeeScript,[12] Curry,[7] Elm, Escher,[13] F#,[14] Frege,[15] Hack,[16] Idris,[17] Isabelle,[7] Java/Generics,[7] LiveScript,[18] Mercury,[7] PureScript,[19] Python,[7][20] Raku,[21] Rust,[22] Scala,[7][23] Swift,[24] Timber,[25] Visual Basic 9.0[7][8] |
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。
Haskell是现有的一门开放的、已发布标准的,且有多种实现的语言。[37]支持惰性求值、模式匹配、列表解析、类型类和类型多态。它是一门纯函数式编程语言,这意味着大体上,Haskell中的函数没有副作用。Haskell用特定的类型来表达副作用,该类型与函数类型相互独立。纯函数可以操作并返回可执行的副作用的类型,但不能够执行它们,只有用于表达副作用的类型才能执行这些副作用,Haskell以此表达其它语言中的非纯函数。
Haskell拥有一个基于Hindley-Milner类型推论的静态、强类型系统。Haskell在此领域的主要创新就是加入了类型类,原本设想作为重载的主要方式,[39]在之后发现了更多用途。[40]
Haskell的主要实现GHC是个解释器,也是个原生代码编译器。它可以在大多数平台运行,GHC在并发和并行上具有高性能的实现能力,[41]也有丰富的类型系统,如广义代数数据类型和类型家族)。
单子是一个抽象类型,可以表达不同种类的计算,包括异常处理、非确定性、语法分析以及软件事务内存,其中一个应用是用于表达副作用的类型。单子定义为普通的数据类型,同时Haskell也为其提供了几种语法糖。
Haskell有一个活跃的社区,在线上包仓库Hackage上有丰富的第三方开源库或工具。[42]
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常用的函数:
list comprehension是指基于一个List,按照规则产生一个新List,例如:[x*2 | x <- [1..10], x*2 >= 12]
使用( )与逗号分隔符,定义一个tuple的实例。其元素可以使不同类型,但个数是固定的。
Tuple的类型取决于其中项的数目与其各自元素的类型。单元素的 Tuple 被禁止。
基本类似于C语言。但使用not表示逻辑非。
+ - * / ^ -- 加、減、乘、除、指數 mod -- 取餘數 $ -- 也是表示函數作用的, 但它的優先級最低, 而且作用次序是從右向左的 ++ -- 兩個List的連接 . -- 函數的複合 && || == /= -- 與、或、等於、不等於 <= >= < > -- 小於等於、大於等於、小於、大於 : // = @ -- 一個元素連接List、 -> -- 函數類型描述,運算符左邊為參數類型,右邊為結果類型。為右结合。例如:addThree :: Int -> Int -> Int -> Int => -- 運算符的左邊表示一個類型變量(通常為單個小寫字母)屬於一個類型類(Typeclass),相當於C++語言的模板參數類型 .. -- List的Range限定 :: -- 函數/表達式的類型特徵,左側為函數/表達式,右側為類型 <- -- List comprehension 的條件限定 !! -- 取List的第某個元素,下標從0開始
基本的 Typeclass:
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。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的函数。高阶函数的使用在一些情况下将极大的简化代码。