3天学习haskell---haskell第三天

一类

1 自定义类型

data关键字用来定义自己的数据类型 (类型必须大写)

data Rank=Ten | jack |queen |king |ace

2 多态

(1)backwards::[a]->[a]

backwards []=[]

backwards (h:t)=backwards t ++ [h]

[a]的类型是一个元素为任意类型的列表。

(2)data Triplet a =Trio a a a deriving (Show)

a是一个类型变量。表示具有相同类型的三元组是Triplet类型的。(deriving (Show)   用来显示数据)

3递归类型

树的定义:节点,要么是叶子,要么是树的列表。值在叶子上。

data Tree a=Children [Tree a] |Leaf a deriving (Show)

Tree是类型构造器,Children ,Leaf 是数据构造器

使用:

let tree =Children [Leaf 1,Children[Leaf 2,Leaf3] ]

tree

求树的深度:

depth(Leaf _)=1

depth(Children c)=1+max(map depth c)

4类 (类似于操作)

不同于面向对象编程中的概念,类定义了哪些操作可以在那些输入上进行。用来精细控制多态和重载。

如果类型支持类的所有函数,那么这个类型是类的一个实例。



二  monad [1]

1  定义

(1)monad 作用是 以一种特定属性的方式组合函数

 (2) Haskell 中把单子定义成一个 type class

class Monad m where

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

  return :: a ->m a

对任一个型别建构元 m,如果我们能定义出 >>=  return 两个函数,并满足后述的一些条件,m 就是一个单子。对 type class 我们不在此解释太多,只需知道这是 Haskell允许我们重载 (overload) >>=  return 等符号、使许多型别的类似函数可以用同一组符号表示的机制就可以了。型别 m a 代表一个「结果为型别 a 的运算」,运算过程中可能产生单子 m希望描述的副作用,如同 Maybe a 用来表示一个结果是 a,但可能会产生例外的运算。函数return 把一个型别为 a 的值「提升」到 m a.  Maybe 的例子中,return 就是 Just.函数>>= 则是提升到 m 之上的函数应用,左手边是一个 m a,右手边是一个从 a  m b 的函数;x >>= f 大致上的意思是执行 x 代表的运算,如果得到一个型别是 a 的值,把他传给f.结果的型别是 m b

 (3)主要包括:

1 类型构造器-定义一个类型。

2 return 返回。

3 >>= 操作, 该操作  接受一个类型的实例  和 一个函数 (实例写在>>=前边,函数写在>>=后边),输出一个结果.

例如,

(>>=) : Maybe a -> (a -> Maybe b) -> Maybe b

(Just x) >>= f = f x

Nothing >>= f = Nothing


例如Maybe monad的定义,

data Maybe a=Nothing | Just a (Just是一种特定的类型)

instance Mnoad Maybe where

return  =just

Nothing >>=f =Nothing

(Just x)>>= f =f  x


2 单子定理

单子定律

单子的 return  >>= 两个函数当然不能随便写。型别建构元 m 要称为单子,return >>= 须满足下面三个定律:

1.    (return x) >>= f == f x,

2.    m >>= return == m,

3.    (m >>= f) >>= g == m >>= (\x -> f x >>= g).

头两条定律谈到 return 的合理行为。第一条确保 return x 不含副作用: (return x)>>= f 应该和把 x 直接丢给 f 一样。第二条定律中,把 m 的结果直接 return 回来并不改变其值。第三条是 >>= 的递移律,可和函数组合的定义比较:

f (g x) = (f . g) x

这条定律确保 >>= 的行为确实类似函数应用。

读者可以检查一下 Maybe  return  >>= 确实满足这三条定律。目前 Haskell 本身无法自动检查这三条定律,只能希望程序员遵守。程序员照理说也希望所有单子都满足这些定律,以便于程序的推理分析。不幸的是确实有些很有用的「单子」为了效率因素会在某些情况下违反单子定律。以后有机会再谈。

 

3 列表单子

我们进一步扩充算式,添加一个 Or 操作数:

data Expr = Num Int | Neg Expr | Add Expr Expr
          | Div Expr Expr | Or Expr Expr

此处的Or 倒不是逻辑上的「或」,而是非确定(non-deterministic)运算:Or e1 e2 的值可能是 e1,也可能是 e2.非确定操作数有点牵强地用在这里仅是为了举例,但在程序语言的应用中,需要处理非确定性的情形并不少见。例如一个文法 parse一段句子,可能有不只一种 parse的方法。我们会需要写程序找出所有 parse.

我们希望找出一个算式所有可能的值,因此 eval 的型别改为 Expr-> [Int],将所有可能的值放在一个串行中。串行是另一个单子的常见例子。函数 return 将一个型别为 a 的值 x提升成串行,自然的选择是传回 [x],因为 x 是自己的唯一值:

return :: a -> [a]
return x = [x]

假设 xs 是一个型别为 [a] 的串行,函数 f 拿一个 a,将所有的可能结果放在型别为 [b] 的串行中。我们如何把 xs 喂给 f 呢?答案是先用 f 处理 xs 里的每个元素,再把结果接起来:

(>>=) :: [a] -> (a -> [b]) -> [b]
xs >>= f = concat (map f xs)

写成 list comprehension可能更清楚xs >>= f = [y | x <-xs, y <- f x].注意 return >>= 的型别分别是 a-> [a]  [a]-> (a -> [b]) -> [b],符合 Monad class的要求。(此处为了说明而标上型别。在 Haskell中,由于型别已在 class宣告中给过一次,在此是不加型别的。)

函数 eval 该怎么处理新加的 Or 呢?Ore1 e2 的所有可能值是 e1 的所有可能值以及e2 的所有可能值,所以:

eval (Or e1 e2) = eval e1 ++ eval e2

而对于 NumNegAdd 等情况,很幸运地,eval 可在不修改的情况下直接使用新的 return >>= 定义。例如

eval (Add (Or (Num 1) (Num 3)) (Or (Num 3) (Num 4)))

的值会是 [4,5,6,7].

至于 Div 呢?函数 eval  Div 条款用了一个 mzero,当时的值是 Nothing.在此处只要把mzero 设定为 [] 就可以了 -- 分母为零时 Div 没有值,因此我们传回空串行。

串行单子在此处用来表达非确定性计算,有时当我们说「非确定性单子」时,指的是串行。在 Haskell这种缓式语言中,串行尾端在需要时才会算出来,因此 eval 的行为像是回溯 (backtracking):先用第一个值去试试看,如果可用就一直使用下去,如果失败则回溯回来换成下一个值。读者可以试试看 eval(Div (Num 4) (Add (Neg (Num 2)) (Or (Num 0) (Num 2)))) (4 / (-2 + (0 OR 2))) 是怎么算出来的。有人也因此把串行称作「回溯单子」。但不论非确定性或回溯还另有其他数据结构可表示(有的也许会特别强调公平性或效率等等因素)。此处我们还是简单地称之为串行单子。

 

参考 [1] http://www.iis.sinica.edu.tw/~scm/ncs/2009/11/a-monad-primer/

参考 [2]http://cookoo.iteye.com/blog/27006

参考 [3]http://zhiwei.li/text/2011/05/haskell%E7%9A%84monad/


posted @ 2012-12-05 15:25  唐僧吃肉  阅读(266)  评论(0编辑  收藏  举报