550 likes | 799 Views
Hi Haskell. “A language that doesn’t effect how you think about programming is not worth learning”. Alan Perlis. 提纲. 概述 若干语言特性(是什么让 Haskell 如此独特?) Lambda , Curry Algebraic Data Type , Type Class Purity, Lazy Evaluation 也许来个中场休息? (optional) 对并行与并发的支持 若干例子以及性能问题 Haskell 在工业界的应用 总结.
E N D
“A language that doesn’t effect how you think about programming is not worth learning” Alan Perlis
提纲 • 概述 • 若干语言特性(是什么让Haskell如此独特?) • Lambda,Curry • Algebraic Data Type,Type Class • Purity, Lazy Evaluation • 也许来个中场休息?(optional) • 对并行与并发的支持 • 若干例子以及性能问题 • Haskell在工业界的应用 • 总结
编程范式(Programming Paradigm) 令一门编程语言区别于其它语言的根本在于它所提供的编程范式 编程范式指的是编程活动中所遵循的根本风格。它包括选取何种基本元素来构成程序(比如对象,函数,变量,约束条件等等)以及用何种方式来表达计算过程(比如赋值、表达式求值,数据流等等)。--维基百科
你知道并用过多少种编程范式? Imperative Declarative Object-Oriented Aspect-Oriented Event-Driven Functional Generic Logic Concurrent
Functional Programming 在FP这种编程范式里,计算是通过对函数的求值(evaluation)来组织的,它尽量避免涉及状态与可变(mutable)数据。FP强调的是函数的施用(application),与之相对的命令式编程强调的则是状态的改变。 几乎所有的主流语言都是偏向命令式的。
简单来说,Haskell 是一门通用的、静态强类型的、 pure(该叫纯粹还是纯洁呢?)的、函数式编程语言。 它提供的最基本的编程范式是FP,另外还能支持其它多种范式。
Lambda表达式(匿名函数) (\x y->x*10+y) 5 9 --59 大部分时候可以象动态语 言那样不写类型声明,编 译器会尽全力推导出表达 式的类型,如果推导失败, 它会抱怨的 //JavaScript (function (x,y){ return x*10 + y })(5,9)
First-Class Function & High Order Function • 函数是语言中的一等公民,意味着它享有和Int、Float这些基本类型一样的公民权利: • 函数本身可以作为参数传递 • 函数本身可以作为返回值 • 函数本身可以作为复合类型数据的一部分 • 高阶函数是这样一个东东: • 或者它的参数是另外的函数 • 或者它的返回值是一个函数
Currying(Partial Application)&High Order --f::Int->Int->Int --f::Int->(Int->Int) f x y=x*10 + y f5 = f 5 f7 = f 7 f5 9 -- 59 f5 1 -- 51 f7 9 -- 79 f7 1 -- 71 //JavaScript function f(x) { return function(y) { return x * 10 + y } } f5 = f(5) f7 = f(7) f5(9) // 59 f7(1) // 71
高阶函数的应用 - 函数式3D建模 参数化曲面 (R,R) -> (R,R,R) 隐式曲面和空间区域:(R,R,R) -> R,通过计算结果的符号来判断是否在区域内 高度图:(R,R) -> R 2D曲线: R -> (R,R) 变换: (R,R,R) -> (R,R,R) 图像: (R,R) -> Color
Example - FieldTriphttp://www.haskell.org/haskellwiki/FieldTrip talk is cheap show me the running code
Example - FieldTrip circle :: Curve2 semiCircle :: Curve2 -- 让曲线绕着Z轴转一圈 revolve :: Curve2 -> ParamSurf torus :: R -> R -> ParamSurf -- sr:内径 cr:截面半径 torus sr cr = revolve (const (sr,0) ^+^ const cr *^ circle) sphere = revolve semiCircle
Example - 函数式3D建模 采用类似的思想,我们还能描述更复杂的3D场景
Algebraic Data Type 带一个参数的Constructor 递归的类型定义 不带参数的Constructor • data是关键字,用来定义新的数据类型,可以带零或多个类型参数 • 每个新类型有一或多个constructor用来创建该类型的值 • 每个constructor有零或多个参数,参数的类型可以是类型参数指定的类型以及任意可见类型 data Maybe a = Just a | Nothing data Tree a = Empty | Leaf a | Node (Tree a)(Tree a)
Algebraic Data Type • 用具体的参数类型来具化Maybe:Maybe Int • Just 20 • Nothing • 当我们拿到一个Maybe Int的值,如何知道它包裹的整型数到底是多少?
Pattern Matching • constructor可以作为函数来创建值 • 也可以用在pattern里萃取被包裹的值 tree = Node (Leaf 4) (Node Empty (Leaf 5)) accum Empty = 0 accum (Leaf a) = a accum (Node left right) = (accum left) + (accum right) accum tree -- 9
某种多态的函数 • 试着定义这样一个函数,它判断某个值是否在list里 • 作为懒惰的程序员,我们当然希望函数的定义只写一次 • 问题来了,我们怎么样描述这样一种数据类型,它的值是可以进行相等性测试的?
Type Classes OO的class class Eq a where (==) :: a -> a -> Bool elem :: Eq a => a->[a]->Bool elem k (x:xs) | k == x = True | otherwise = elem k xs elem k [] = False
Instance of Type Classes instance Eq (Maybe a) where Just v1 == Just v2 = v1 == v2 Nothing == Nothing = True _ == _ = False
Lazy Evaluation a = [1..] -- infinite list take 3 a -- [1,2,3]
Purity 好吧,Functional我早就明白了, 可“纯粹”是什么?
Purity variable一旦和某个数据绑定了就不可再修改了, 所以它不再是可变的量,而仅仅是一个不可变的值的名 字。 a = 3 a = 19
Purity 函数不会有side effect,不会对数据做破坏性的修改,不会 改变外部世界的状态,它的输出只依赖它的输入,任何时刻, 给定同样的输入,一定得到同样的输出。数学中的函数从来就具 有这样的性质,可在大多数编程语言中这种美好的性质却被毁了。
Referential Transparent 能用2 * f(x) 来替换f(x) + f(x)吗? int y = 10; int f(int i) { return i + y++; } f(5) + f(5); // 15 + 16 = 31 2 * f(5); // 2 * 15 = 30
能用来做事吗? 如果连状态都不能改变,那它有个鸟用! 不要误会,所谓不能改变状态指的是函数求值的时候不允许改变状态,并不意味着程序运行的时候不能改变状态 这是什么意思?
IO Action --putStrLn :: String -> IO () --main :: IO () main = putStrLn "Hi Haskell!" • IO ()是一种IO动作,它是普通的first-class的数据,如果它被执行就会做某些输入输出操作,IO动作只能被其它的IO动作嵌套,main是最外层的IO动作,也是程序的入口点 • 一个IO动作在创建的时候并不会执行,只有在嵌套它的外层动作被执行时才会跟着执行,所以执行和函数求值是不同的 • 所有的IO动作都是由运行程序开始执行main而引发的
Monad • 所有会引发状态改变的动作(包括IO Action)都是一种Monad。 • 听起来象是Monster! • Haskell最大的错误是把Monad叫作Monad!或许应该喊它 "warm fuzzy thing" (暖毛毛)! --Simon Peyton Jones,Haskell设计者之一
Monad Monad m a a inject (a -> m b) bind class Monad m where bind :: m a -> (a -> m b) -> m b inject :: a -> m a
Example - 函数式音乐编程 • 创建一种DSL,既能表达高层的音乐结构,也能表达底层的声音信号处理。 • 什么特性特别有助于在Haskell里创建EDSL? • 纯洁的函数和高阶函数 • Algebraic Data Type • 强类型检查 • 惰性求值 • 二元函数的中缀写法
Example - 函数式音乐编程, Hommage Haskell Offline Music Manipulation And Generation EDSL http://substitut-fuer-feinmotorik.net/projects/haskellommage
线程 • 非操作系统线程,极为轻量 • 你可以成千上万地随便创建 forkIO :: IO () -> IO ThreadId forkIO (writeFile "temp.txt" "haskell thread")
Shared Mutable State Concurrency • Race conditions due to forgotten locks • Deadlocks resulting from inconsistent lock ordering • Corruption caused by uncaught exceptions • Lost wakeups induced by omitted notifications
Software Transactional Memory(STM) • 一组动作可以作为一个transaction被执行以保证原子性,一旦线程进入这个原子块,那么其它的线程在该原子块退出之前无法看到其做的修改,同样,这个线程在此期间也无法看到别人做的修改 • 在退出原子块的时候,结果要么是a要么是b • transaction成功执行,其它线程可以立刻看见它所做的状态修改 • transaction执行失败,所有的修改被抛弃
STM Sample import Control.Concurrent.STM type Bal = TVar Float transfer::Float->Bal->Bal->STM () transfer qty fromBal toBal = do fromQty <- readTVar fromBal toQty <- readTVar toBal writeTVar fromBal (fromQty - qty) writeTVar toBal (toQty + qty)
事务执行 transferTest = do alice <- newTVar 12 bob <- newTVar 4 transfer 3 alice bob -- atomically:: STM a -> IO a main = atomically transferTest
Composable STM orElse :: STM a -> STM a -> STM a retry :: STM a throwSTM :: exception -> STM a catchSTM :: STM a -> (exception->STM a) -> STM a instance Monad STM complexAction = action1 `orElse` action2 main = atomically complexAction
让我们并行地工作吧! -- 一个顺序程序 sort :: (Ord a) => [a] -> [a] sort (x:xs) = lesser ++ x:greater where lesser = sort [y | y <- xs, y < x] greater = sort [y | y <- xs, y >= x] sort _ = []
让我们并行地工作吧! -- 一个并行程序 import Control.Parallel (par, pseq) parSort :: (Ord a) => [a] -> [a] parSort (x:xs) = force greater `par` (force lesser `pseq` (lesser ++ x:greater)) where lesser = parSort [y | y <- xs, y < x] greater = parSort [y | y <- xs, y >= x] parSort _ = []
并行FP并不遥远 Map-(Filt)-Reduce(Haskell里叫Fold),是FP里司空见惯的pattern Mapper、Filter和Reducer在处理某个数据集合时并不依赖于其它的数据集合,天生就适合并行化
并行FP并不遥远 • 所以,Google将此pattern发扬光大,用于大规模并行化数据处理,打造了经典的Map/Reduce编程框架(尽管采用了C++来实现),和Google File System、BigTable一道成为Google搜索技术的三大基石 • Hadoop, Map/Reduce的开源实现,为无数互联网公司所使用 • 其实我们每天都在享受着FP提供的服务
用Haskell做一个简单的MapReduce框架 import Control.Parallel.Strategies mapReduce :: Strategy b -- evaluation strategy for mapping -> (a -> b) -- map function -> Strategy c -- evaluation strategy for reduction -> ([b] -> c) -- reduce function -> [a] -- list to map over -> c mapReduce mapStrat mapFunc reduceStrat reduceFunc input = mapResult `pseq` reduceResult where mapResult = parMap mapStrat mapFunc input reduceResult = reduceFunc mapResult `using` reduceStrat
异构并行计算 绝大多数的个人电脑中都有一个强劲的并行处理器:显卡中的GPU! 没有道理限制代码只能在CPU上奔跑 对GPU编程:Shader、CUDA、OpenCL GPU的执行模型天生就适合FP GPipe,一种EDSL,也是一种函数式的GPU编程模型,直接用Haskell写GPU程序,从而利用Haskell的种种迷人特性,运行时动态生成OpenGL的GLSL,提交给显卡执行
Example - 函数式GPU编程 GPipe http://www.haskell.org/haskellwiki/GPipe
Parser Combinator 每个程序员或多或少都会干点parse的工作 所谓parse,其实就是将一段无结构的数据(通常是线性的字符串、二进制流)转换成定义良好的结构化数据 有许多工具(比如Lex、Yacc、Flex、Bison、Antlr)自动生成parse代码 Haskell社区首先探索了一种独特的方式来构建复杂的parser,称为Parser Combinator。这种思想纷纷为许多其它语言所借鉴。
Example - Parser Combinator for JavaScript • parser是这样一个函数:String -> Result • Result是一个对象,包含3个字段: • remaining: 剩下的有待parse的字符串 • matched: 被该parser成功parse的字符串 • ast: parse后生成的抽象语法树 • 有若干预定义的基本的parser,比如 • range("0", "9") • token("begin") • parser可以用combinator组合起来,得到一个新的(更复杂的)parser • repeat1(alternate(sequence(parser1, parser2), parser3))
这些EDSL的共同点 把你对特定问题的“描述”送进头等舱,成为first-class的 结构化数据,为其选择合适的combinator,使之不断组合产生 更复杂的方案,直到能完全描述出最终的程序意图。
性能 • 天下武功,唯快不破 --火云邪神 • The Computer Language Benchmarks Game http://shootout.alioth.debian.org/ 平均来说,Haskell的速度是C的1/5 • 眼见为实——2D水波特效 http://blog.csdn.net/soloist/archive/2009/04/09/4060081.aspx