Hi Haskell

Preview:

DESCRIPTION

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 在工业界的应用 总结. - PowerPoint PPT Presentation

Citation preview

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 在工业界的应用• 总结

编程范式 (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

data 是关键字,用来定义新的数据类型,可以带零或多个类型参数

每个新类型有一或多个 constructor 用来创建该类型的值

每个 constructor 有零或多个参数,参数的类型可以是类型参数指定的类型以及任意可见类型

data Maybe a =

Just a

| Nothing

data Tree a =

Empty

| Leaf a

| Node (Tree a)(Tree a)

递归的类型定义

带一个参数的 Constructor

不带参数的 Constructor

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

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

OO的class

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

IO () 是一种 IO 动作,它是普通的 first-class 的数据,如果它被执行就会做某些输入输出操作, IO 动作只能被其它的 IO 动作嵌套, main 是最外层的 IO 动作,也是程序的入口点

一个 IO 动作在创建的时候并不会执行,只有在嵌套它的外层动作被执行时才会跟着执行,所以执行和函数求值是不同的

所有的 IO 动作都是由运行程序开始执行 main而引发的

--putStrLn :: String -> IO ()

--main :: IO ()

main = putStrLn "Hi Haskell!"

Monad

• 所有会引发状态改变的动作(包括 IO Action )都是一种 Monad 。

• 听起来象是 Monster ! • Haskell 最大的错误是把 Monad 叫作 Monad !或

许应该喊它 "warm fuzzy thing" (暖毛毛 )! -- Simon Peyton Jones, Haskell 设计者之一

Monad

class Monad m where

bind :: m a -> (a -> m b) -> m b

inject :: a -> m a

Monadm a

bind

injecta

(a -> m b)

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 locksDeadlocks resulting from inconsistent lock ordering

Corruption caused by uncaught exceptions

Lost wakeups induced by omitted notifications

Software Transactional Memory(STM)

一组动作可以作为一个 transaction 被执行以保证原子性,一旦线程进入这个原子块,那么其它的线程在该原子块退出之前无法看到其做的修改,同样,这个线程在此期间也无法看到别人做的修改

在退出原子块的时候,结果要么是 a 要么是 ba. transaction 成功执行,其它线程可以立刻看见它所做的

状态修改b. 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

真有人用它来做产品吗?

都有谁呢?

本次分享的起因

前台技术中心要成立兴趣小组学习一门新语言,在比较过多门语言后,选择了 Haskell

FP 正在深刻地影响工业界。比如 Scala ,它的目的是将 Haskell 的思考方式带给 Java 程序员,已经成为JVM 上的主流语言,正在成为其上的统治性语言

无论你常用的是什么语言,掌握一门 FP尤其是Haskell 这样的纯粹的 FP ,都会使你成为更好更好的 programmer

关于我

邓际锋soloist.deng@gmail.com

http://blog.csdn.net/soloist

个人研究兴趣包括编程语言的设计与实现交互式艺术及 FP 在该领域的应用 如何为儿童和非技术人员开发富有乐趣的创作工具

谢谢!

Recommended