functional programming

1.

functional programming 

2. reducible expression(redex):for example, a function call with all of its arguments supplied is a redex, but a constant is not. 

3.functional language is NOR

functional programming

functional programming 

4.

functional programming

functional programming 

 

5.

functional programming 

functional programming

functional programming 

 

 

 

 

 

6.

functional programming

functional programming 

 

 

 

 

 

 

 

 

 

 

7.

functional programming

functional programming 

 

 

 

 

 

 

 

 

 

 

 

 

8.

functional programming 

functional programming 

这里用data关键字构造了一个新的类型Tree,它有两个值构造器Empty和Node,Empty不接收任何参数,Node构造器接收一个Tree类型,一个Int类型,一个Tree类型的参数(可以将值构造器看作一个函数)
表示Tree类型的组成成分可以是空,也可以是Tree Int Tree

emptynode = Empty (用Empty构造器构造了一个Tree)
node = Node emptynode 1 emptynode (用Node构造器构造了一个Tree)

 

 

 

 

 

 

1.
functional programming
functional programming

functional programming

2.
functional programming
3.
functional programming
functional programming

 

4.
functional programming
functional programming
5.
functional programming
31 = 2^5 -1  30 = 2*(2^4-1)..

 

 

 

 

 

 

func :: Eq a ⇒ a → a → Bool
上面是func函数的函数签名,a → a → Bool部分表示它接收两个任意类型的参数,输出一个Bool类型的数,而前面的Eq a ⇒ 部分则对任意类型a做了限制,表示它也并不完全是任意的类型,而是要属于Eq这个type class的类型才行(Eq叫做type class,可以类比于Java中的接口)这个函数签名的完整意思是接收两个实现了Eq接口的类型的参数,输出Bool类型的数

那么如何让自己的类成为Eq type class呢?或者说如何让一个类实现Eq接口呢?
我们首先看下Eq接口是怎么定义的,它要求我们实现哪些方法:
class Eq a where
    (==) :: a -> a -> Bool
    (/=) :: a -> a -> Bool

这是Eq接口的定义,它要求我们至少需要实现方法(==)
那么对于类型data Color= Red | Green | Blue我们来让它实现Eq接口的(==)方法
instance Eq Color where
 (==) Red Red = True
 (==) Green Green = True
 (==) Blue Blue = True
 (==) _ _ = False

即当传入的两个参数都由同个Color构造器构造时,输出为True,其余情况输出False

另:除了上述方式实现接口,Haskell还提供了快速实现接口的方式,如
data Color= Red | Green | Blue deriving Show,则让Color类使用系统默认的方式实现了Show接口,只有实现了Show接口的类,才能让系统知道如何将该类打印出来,下面是Show的接口定义,你也可以自己实现它,让它以自己想要的方式显示在屏幕上
class Show a where

show ::a -> String

2.接口Ord的定义为class Eq a => Ord a where...表示要实现Ord接口,得首先实现Eq接口..

 

 

 

1.Semigroup: 是一个type class,即也是Haskell定义好的一个接口,

class Semigroup a where
 (<>) :: a → a → a
 sconcat :: NonEmpty a → a

这是接口的定义,要让自己的类实现该接口则最少需要实现(<>)函数(associative binary operation)
其中NonEmpty定义是 data NonEmpty a = a :| [a] 即 (:|) a [a],[:|]是NonEmpty类的值构造器,该类用来表示非空的List

2.Haskell中的数组是Semigroup,其定义的二元操作符是++,因为对于任意的三个[a] x,y,z,(x++y)++z==x++(y++z)

其实现为:
Instance Semigroup [a] where
 (<>) = (++)

所以[1,2,3] <> [4,5,6]的输出为[1,2,3,4,5,6]

3.Haskell的Maybe类也是Semigroup,
data Maybe a = Just a | Nothing (Maybe,Just,Nothing就是这么个东西)

其实现为
instance Semigroup a => Semigroup (Maybe a) where
 (<>) Nothing y = y
 (<>) x Nothing = x
 (<>) (Just x) (Just y) = Just (x <> y)

对于任意的三个Maybe a,x=Just [1,2]  y=Nothing  z=Just [3,4],
(x<>y)<>z=Just [1,2] <> Just [3,4]=Just ([1,2] <> [3,4])=Just [1,2,3,4]
x<>(y<>z)=Just [1,2] <> Just [3,4]=Just ([1,2] <> [3,4])=Just [1,2,3,4]

4.对于元组 ([1], Nothing) <> ([2, 3], Nothing) 输出 ([1,2,3], Nothing)(各自调用各自实现的(<>)函数)

5.((1:) <> tail) [4, 5, 6] (1:)和tail都是函数,(1:) [4,5,6]输出[1,4,5,6],tail [4,5,6]输出[5,6],
这两个函数由<>运算后成为新的函数,对于输入[4,5,6],输出[1,4,5,6, 5,6]

6.Semigroup里的另一个方法sconcat的默认实现是接收一个非空数组,然后用你定义的二元操作符将数组中的每个元素连起来,计算出结果
sconcat (Just [1] :| [Just [2], Just [3]]) = Just [1] <> Just [2] <> Just [3] = 
Just ([1]<>[2]<>[3]) = Just [1,2,3]

7.Monoid:也是type class,它的定义为
class Semigroup a => Monoid a where
 mempty :: a
 …
该接口至少需要实现mempty方法。它是Semigroup的子类,即要实现Monoid接口则必须先实现Semigroup接口

8.Haskell的数组同时也是Monoid,其实现为
instance Monoid [a] where
 mempty = []
对于任意的[a] x,要满足mempty <> x == x <> mempty == x,
如对于[1,2,3],[] <> [1,2,3] = [1,2,3] <> [] = [1,2,3]

9.Functor也是一个type class,它的定义是

class Functor f where
    fmap :: (a -> b) -> f a -> f b

他要求实现fmap函数,这个函数接收一个可以将类型a转为类型b的函数,和一个被盒子包裹着的类型a(如data Maybe a = Just a | Nothing,Just 2就是被Just这个盒子包裹着的Int类型,可以将盒子看作是类型的值构造器)。该函数将类型a从盒子f中拿出来,应用函数将a转为b后重新放进盒子f里返回。

如Maybe类型,它默认实现了Functor这个type class:

instance Functor Maybe where 
fmap _ Nothing = Nothing 
fmap f (Just x ) = Just (f x )

Just和Nothing是Maybe类型的值构造器,相当于前面说的盒子。

注:对于data Maybe a = Just a | Nothing,实现Functor的时候是对Maybe实现而不能对Maybe a实现,同理对于data Class a b = ClassValueConstructor a b只能对Class a实现,而不能是Class或Class a b

实现Functor的时候要使其遵守两个规则,这两个规则遵守与否编译器并不会去检查(就像Semigroup中的<>和Monoid中的mempty要遵守的规则一样),需要编写人员自觉遵守,写出来的Functor才是Functor,不然不是真的Functor

规则一:
例,fmap id (Just 3) = Just 3 其中id是 identity function签名为,即会将输入原封不动输出的函数,id (Just 3) = Just 3,那么fmap id (Just 3)也必须要等于Just 3

规则二: fmap (f . g) F = fmap f (fmap g F)
有两个函数f和g,和他们的合并函数(f.g)(如f x = x + 3  g x = x * 5,则合并后的函数(f.g)为(5 * x) + 3,是先算的g再算f),那么对于某个实现了Functor接口的类F来说,你直接对它fmap (f.g)和先对它fmap函数g再去fmap函数f,它的结果要是一样的。
例,fmap (f . g) (Just 3) = Just 18  fmap f (fmap g (Just 3)) = Just 18

另:(-> r)默认也是个Functor。(-> r)是个啥呢?我们可以把(a->r)看成是(->) a r,则(->)就是一个值构造器,它需要两个参数,来构造函数这个类型。把(-> r)看为一个类型且该类型的值构造器也是(-> r)这个只接收一个参数的值构造器。则它的Functor实现为:

instance Functor ((->) r) where 
fmap f g = (\x -> f (g x))

fmap接收的f就是某个函数(a->b),g则为套在盒子里的值,而这个盒子为((->) r),所以这个盒子配上那个值后,就变成了一个函数,所以g实际上就是一个函数。fmap返回的也要是一个套在盒子里的值,同样的套在((->) r)这种盒子里就变成了函数,所以fmap返回的是一个函数。fmap返回的函数是接收一个参数x并且将x先经过g函数再经过f函数的函数。

因为(-> r)默认是一个Functor,所以fmap允许这样用:fmap (*5) (+3),即直接接收两个函数,其返回的是它们的合并函数(f . g),先算+3再算*5。

(-> r)除了默认是Functor,它默认还是Applicative和Monad,后说

10.Applicative也是一个type class,它的定义是

class (Functor f) => Applicative f where 
pure :: a -> f a 
(<*>) :: f (a -> b) -> f a -> f b

它是Functor的加强版,Functor是将普通的函数(a -> b)应用在被盒子包裹着的东西f a上,而Applicative是将被盒子包裹的函数f (a -> b)应用在被盒子包裹着的东西f a上,<*>就是要将盒子中的函数取出来变成普通函数,后面就和fmap一样了,可以直接调用fmap解决。而pure就是将某个东西套上个盒子

如Maybe的默认实现为:

instance Applicative Maybe where 
pure = Just 
<*> Nothing _ = Nothing 
<*> (Just f) something = fmap f something

Just (+3) <*> Just 9结果为Just 12  pure (+3) <*> Just 9结果也为Just 12

前面的例子都是将只接收一个参数的函数应用到盒子中的值去,那么对于接收多个参数的函数呢?

如怎么把函数(+)应用到Maybe:pure (+) <*> (Just 2) <*> (Just 3)这里结果为Just 5,相当于Just 2和Just 3就是函数(+) 的两个参数,这里就是将Just盒子中的函数(+)取出,将Just盒子中的2和3取出,然后调用函数(+) 得出5后重新放进盒子中返回。

那么具体是怎么样的呢?先看前半部分pure (+) <*> (Just 2),这里相当于Just (+) <*> (Just 2),这里将(+)从盒子中取出,将2从盒子中取出,然后将2作为参数传给函数(+),因为函数(+)是接收两个参数的,这里只接到一个参数,则返回的是函数,即(+2)函数,然后把(+2)放进盒子中返回,所以前半部分得到的是Just (+2),然后再和后面的连起来就是Just (+2) <*> (Just 3),就和之前一样了,是将只接收一个参数的函数(+2)应用给3,放进盒子中返回,结果为Just 5

那么Haskell还提供了一个运算符<$>给我们,这个运算符就是fmap函数,只不过是它的中缀运算符的形式,那么上面pure (+) <*> (Just 2) <*> (Just 3)的写法就可以改写成:
(+) <$> (Just 2) <*> (Just 3)
这里具体怎么理解呢?先看前半部分(+) <$> (Just 2) fmap将2从Just盒子中取出,并将函数(+)应用给它,则返回函数(+2),然后放进盒子中返回,所以返回的是Just (+2),再连上后半部分就变成Just (+2) <*> (Just 3),<*>将函数(+2)和值3从盒子中取出,并用3调用函数(+2)的5,放入盒子中返回,Just 5

这里<$>符号前的函数要是未放在盒子里函数,而后面的参数要是放在盒子中的参数

那么对于已经在盒子中的函数怎么用呢?如对于Just (+),则直接
Just (+) <*> (Just 2) <*> (Just 3)

Applicative也要遵守几个规则,这里只列出最重要的一条为:
pure f <*> x = fmap f x ,即把函数放进盒子里再用<*>和直接把函数给fmap去用要是一样的

另:(+) <$> (*2) <*> (+10)返回的是一个函数,假设为f,则f 3的结果为(3*2) + (3+10) = 19。具体什么原理呢?这里前半部分(+) <$> (*2)即fmap (+) (*2),其返回的是a->a->a函数。如果这里是(+3)而不是(+),则fmap (+3) (*2)是将接收的参数经过(*2)再经过(+3)的函数,但是这个因为是(+),所以是接收一个参数经过(*2)后的值作为了(+)的其中一个参数,但是还需要另一个参数作为(+)的另一个参数,所以fmap (+) (*2)返回的是需要两个参数的函数,a->a->a,其中第一个a会先经过(*2),作为(+)的第一个参数,第二个a作为(+)的第二个参数。
连上后半部分就是函数a->a->a和(+10)做<*>。我们知道签名为a->a的函数(+10)为套在盒子里的值,那么a->a->a即为套在盒子里的函数((a->(a->a))后面的函数(a->a)套在了盒子里)
它的Applicative的默认实现为:
instance Applicative ((->) r) where 
  pure x = (\_ -> x)   
  f <*> g = \x -> f x (g x)
所以对于后半部分,它返回的是一个函数,这个函数接收一个参数x,并且将x作为函数a->a->a的第一个参数,将x经过(+10)的值作为函数a->a->a的第二个参数。
这里f是 f x y = x * 2 + y 即(+) <$> (*2)部分。g是 g x = x + 10。而g x又是作为f的第二个参数y,所以<*>返回的函数就是\x -> x * 2 + (x + 10),则x=3时输出19

11.Monad也是一个type class, 它的定义是

class Applicative m => Monad m where
return :: a -> m a 
(>>=) :: m a -> (a -> m b) -> m b

(我们从Monad的type class定义可以看出来,为什么State和State s a(后面讲到的,可以看后反过来看这里)不能实现为Monad,只有State s能实现Monad。当State s实现Monad时,则上面的m就是State s,m a就是State s a,是一个构造好的类。而如果用State去实现Monad,则m a就是State a,这是一个类型构造器,而非一个类型,它还需要接收一个类型来构造出完整的类)

它的return方法就相当于Applicative中的pure方法,是一模一样的只不过换了个名字,都是用来给某个东西套上盒子

而(>>=)方法和之前的<*>的区别仅仅在于接收的函数不同,Applicative接收的是被盒子包裹着的(a->b)函数,而Monad接收的是(a->m b)函数

那么对于接收多个参数的函数如何应用(>>=)呢?

Just 2 >>= (\x -> Just 3 >>= (\y -> Just (x + y)))

将其分下行,可以写成:

Just 2 >>= (\x -> 
Just 3 >>= (\y -> 
Just (x + y)))

可以理解为,函数(x+y),它的第一个参数为第一行的2,第二个参数为第二行的3
结果为Just 5

那么具体怎么理解呢?这个式子,将Just 2喂给函数:\x -> Just 3 >>= (\y -> Just (x + y)),这个函数接收一个参数x,返回Just 3 >>= (\y -> Just (x + y)),而这个式子意思又是将Just 3喂给函数:\y -> Just (x + y),该函数接收一个参数y,返回Just (x + y)(这里用到了前面的参数x)

那上面这种写法,和直接写func x y = Just (x + y)  func 2 3结果也为Just 5有什么区别呢?
区别在于,第一种写法会对两个参数都进行检查,如果有任何一个为Nothing,则会输出Nothing,而第二种写法遇到Nothing会直接报错。

具体是如何检查的呢?我们先看下Maybe的默认Monad实现为:

instance Monad Maybe where
return = Just   --这里相当于return x = Just x,这里是直接将Just作为函数赋给了return
(>>=) Nothing _ = Nothing
(>>=) (Just x ) f = f x

(>>=)函数会对将放在盒子里的东西取出来调用f函数,那么这里做了模式匹配,如果发现盒子是Nothing,则直接返回Nothing。
对于Nothing >>= (\x -> Just "!" >>= (\y -> Just (show x ++ y))),把Nothing直接为给了一个函数Noting >>= (\x -> ...)则不管喂给了什么函数都是返回Nothing,如Maybe实现的第一个模式匹配,已经和函数f无关了
对于Just 3 >>= (\x -> Nothing >>= (\y -> Just (show x ++ y))),把Just 3喂给了(\x -> ...)函数,则x就变成了3,然后后面Nothing >>= (\y -> Just (show x ++ y))把Nothing喂给了函数(\y->...)返回Nothing,所以就变成了(\x -> Nothing),这里x是3,但是已经无所谓了,返回Nothing

12.do语法:

do 
x <- Just 2 
y <- Just 3 
Just (x + y)

该语法的最后一行要为套在盒子里的东西,其他行都要是套在盒子里的值

13.Monad接口中还有一个fail方法,它的默认实现是fail msg = error msg,即报错,而Maybe对其的实现为fail _ = Nothing,所以当执行

 do 
(x:xs) <- Just "" 
return x

此时因为传入了空串,取不出第一个元素,所以会fail,输出Nothing
14. Haskell中List默认实现了Monad:

例:do
x <- [1, 2] 
y <- [‘a’,‘b’] 
return (x, y)

结果为[(1, ‘a’ ),(1, ‘b’), (2, ‘a’),(2, ‘b’)]

它会将x数组中的元素和y数组中的元素一一送进函数return (x, y)中,得return (1, ‘a’)

return (1, ‘b’) return (2, ‘a’) return (2, ‘b’),得[(1, ‘a’ )] [(1, ‘b’)] [(2, ‘a’)]

[(2, ‘b’)],然后将这4个放进数组中,得[[(1, ‘a’ )], [(1, ‘b’)], [(2, ‘a’)], [(2, ‘b’)]],然后对这个数组调用concat,得[(1, ‘a’ ),(1, ‘b’), (2, ‘a’),(2, ‘b’)]

15.Monad也有三个规则要遵守,不详述

另:(-> r)也是Monad

例:

do 
a <- (*2) 
b <- (+10) 
return (a+b)

首先将(*2)和(+10)从盒子(-> r)里取出来,怎么取呢?首先,我们说函数可以看作是一个装在(-> r)盒子里的值,那个盒子是(-> r)其实是只接收了参数,没有返回值的东西,即只有输入而没有输出,而装在这里面的值就是函数的输出,输入和输出共同构造出了这个函数。所以从盒子里取出的值是*2的返回值以及+10的返回值。
然后将return (a+b)也从盒子里取出,也就是(a+b)了。然后用*2的返回值和+10的返回值作为函数f x y = x + y的两个参数x和y,则该do语句返回的是函数f x = (x*2) + (x+10)

State Monad:
newtype State s a = State { runState :: s -> (a,s) }

instance Monad (State s) where 
  return x = State $ \s -> (x,s) 
  (State h) >>= f = State $ \s -> let (a, newState) = h s 
         (State g) = f a 
      in g newState

我们用s -> (a,s)来表示需要改变状态的函数,如出栈函数f (x:xs) = (x, xs),它接收一个栈,返回栈顶元素和剩下的元素组成的元组,它就是s->(a, s)类型的,s是状态,对应这里的栈,输入和输出的栈是不同的,相当于改变了栈的状态,a则是返回值,对应这里的栈顶元素
那么State就是用来表示会改变状态的操作。

具体实现,对于传进去的状态s,对其首先执行下(State h)的改变状态操作,即上面的代码        (a, newState) = h s 部分,这时候就把a从盒子里取出了,然后用这个取出来的a去调用函数    (a -> State s b),得到新的改变状态操作State g,然后对执行(State h)更新后的状态newState执行操作State g(这一步就是为了取出加粗部分说的b),得到最终的返回值以及状态,然后再包进State s盒子里返回

建:实现的时候写出具体类型签名,对着签名去实现:
(>>=) :: State s a -> (a -> State s b) -> State s b
以s为Stack,a为Int, b为[Char]为例
(>>=) :: State Stake Int -> (Int -> State Stack [Char]) -> State Stack [Char]
即要将输入为Stack输出为(Int, Stack)的装在盒子里的函数通过函数(Int -> State ...)转为输入为Stack输出为(Stack, [Char])的装在盒子里的函数。那么现在就开始构造这个输入为Stack输出为(Stack, [Char])的装在盒子里的函数来作为(>>=)的结果,首先有了输入Stack,然后看怎么构造(Stack, [Char])这个输出,很明显这个输出中的[Char],需要通过(Int -> State Stack [Char])中得来。那么要调用这个函数,首先得有Int,而这个Int可以通过State Stake Int这个输入为Stack输出为(Int, Stack)的装在盒子里的函数中拿到,即用输入Stack调用State Stake Int中的函数,然后从输入(Int, Stack)中拿到Int,然后就可以用这个Int调用(Int->State ...),得到了State Stack [Char],但是我们现在是要得到(Stack, [Char])这个元组,所以调用State Stack [Char]中的函数就得到(Stack, [Char])这个元组了。

另:根据上面也就可以知道,假如State a = State (a -> a),它的Monad是实现不了的,因为它的(>>=)签名是 State a -> (a -> State b) -> State b,这里要将装在盒子里的a->a函数通过函数(a->State b)转为装在盒子里的b->b函数,而你在构造b->b函数时,你拿到了输入b,但是你没办法调用函数(a -> State b),因为它需要a,而对于这个输入b,你又没办法通过State a中的a->a函数转化得到,所以压根实现不了。

例子:假设对堆栈有pop和push操作用来出栈入栈:
pop :: State [Int] Int 
pop = State $ \(x:xs) -> (x,xs)

push :: Int -> State [Int] () 
push a = State $ \xs -> ((),a:xs)

出栈操作返回栈顶元素以及出栈后的栈,入栈操作返回空和入栈后的栈(因为入栈不需要返回值)
main = do
  x<-push 3
  y<-pop
  pop
该do语句返回一个push和两个pop连在一起的操作,调用该操作:runState main [1,2,3],结果为[2,3],即入栈3出栈3出栈1。这里runState相当于属性获得器getter,
签名为State -> s -> (a,s),所以runState main就获得了操作State,然后用[1,2,3]调用该操作。

这里就没办法用之前讲的do语句的方式去分析了,因为这里最后一行的函数并未用到前面的参数x和y(其实可以直接省略掉x<-和y<-)

这里要展开do语句后用上面解释>>=实现里讲思路分析:
push 3 >>= (\x ->
pop >>= (\y ->
pop))

另例:先从堆栈上取下一个数字,看看他是不是 5 ,如果是的话就把他放回堆栈上,如果不是的话就堆上3跟8 (注意下面a<-pop这行,是a<-pop而不是(a, s)<-pop)
functional programming

functional programming

17. IO Monad
main = do 
  putStrLn "Hello, what's your name?" 
  name <- getLine 
  putStrLn ("Hey " ++ name ++ ", you rock!")
do中的每一行都要是一个IO Action,如putStrLn “..”的类型为IO (),意思是这是个会返回空集的IO操作(因为是输出操作,没有返回值)
而getLine的类型为IO String,即这是个会返回String类型的IO操作

当我们把这些操作放在do中时,它就会依次一个个的被执行,执行完getLine后,会将其返回值绑定给name,所以name是String类型的。而最后一行被执行完后返回的是()类型,所以执行完后的main是()类型的(没执行的时候main是IO ()类型的)

18.
forkIO :: IO () -> IO ThreadId  forkIO接收一个返回值为空的IO操作,然后它会开一个新的线程去执行这个IO操作,开线程后会返回IO ThreadId操作,该操作可以获得它刚开的支线程的Id
threadDelay :: Int -> IO () 给定延时时间作为参数后会返回一个让线程延时的IO操作

functional programming
functional programming
运行程序后,首先程序在getLine等你输入,输入后会把你输入的值传给setReminder函数,获取一个IO ()类型,而forkIO函数接收一个IO ()类型,并会另开一个线程去执行IO ()这个IO操作,然后因为forever的缘故(就把它当无限循环就好了),然后支线程开好后,就又回到getLine等待输入。
SetReminder中的内容就是在支线程中要执行的IO操作,do下的每一行都是一个IO Action,合并成了一个大的IO Action返回(这就是Monad在起作用,它将多个函数合成一个大函数),它会打印出提示内容,然后等若干秒,再打印出一串内容
线程之间并行运行,支线程计算时间,主线程可以继续接收用户输入去开启下一个线程

19. 线程间的通信:MVar
functional programming
functional programming
首先执行newEmptyMVar操作后会拿到一个MVar a给m,然后在支线程中执行了两次putMVar操作,即两次往前面拿到的那个MVar中放值,主线程在开完支线程后还会对刚刚拿出的MVar进行两次读取操作并打印出来。
那么该程序的结果是先输出X在输出Y。
它的执行过程是,主线程取出MVar并开了支线程后,会卡在读取MVar的操作上,直到支线程将X写入后,主线程才能读取MVar中的值,而在主线程读取完MVar中的值之前,支线程中第二个写入操作会一直被卡住,直到主线程把X读出来了才能再往MVar中放入Y。同样的,主线程中的第二个读取操作也一定会被卡住直到Y被写入后才能进行读取操作。所以该程序的结果无论如何都一定是先输出X再输出Y。
若上面支线程中只有一个写入操作,则主线程会一直卡在第二个读取操作上,几秒后会超时抛异常

另:Haskell的forkIO出来的支线程即使在主线程已经返回的情况下仍会继续运行,并不像其他语言的多线程,一般主线程会等支线程结束后再返回,Haskell的主线程是直接返回让支线程继续运行的。

20. STM(Software Transactional Memory)

STM可以看作是和IO完全等价的,除了有如下不同:
①我们知道多个IO Action可以在do中拼在一起变成一个大的IO Action,而这个大的IO Action如果执行不成功(如中间连续执行了两次takeMVar超时卡死了),则这个失败的小IO Action之前的IO Action是不会受影响的,是会执行成功的。
而对于STM Action,多个STM Action可以在do中拼在一起变成一个大的STM Action,但是若这个大的STM Action中有任何一个小的Action没执行成功,这个失败的小STM Action之前的STM Action是会回滚的。即中间有任何执行不成功,则整个Action都不会被执行。要么就都执行,要么就都不执行,不会像IO Action那样执行一半。且在执行STM Action过程中,外界是不可见的外界(要么就感觉这个STM Action完全没有执行,要么就直接发现这个STM Action已经全部执行完了,STM的中间过程对外界完全不可见)如一个线程在执行如下的STM Action:
takeTMVar v
takeTMVar v
putTMVar v 2
这个Action连续两次取一个名为v的TMVar(STM中的MVar,②中会讲到),很明显这个线程会卡在第二行的操作上。但是因为STM操作的不可见性,在这整个操作没有执行成功前,外界看它是相当于一行都没执行,所以此时即使锁v已经在这个STM Action中的第一行被拿着了,且现在正卡在第二行,但是另一个线程依旧可以takeTMVar v拿到锁v

②STM中用TVar代替MVar:

newTVar   :: a -> STM (TVar a)
readTVar  :: TVar a -> STM a
writeTVar :: TVar a -> a -> STM ()
这里的newTVar相当于MVar中的newMVar, readTVar相当于MVar中的takeMVar,
writeTVar 相当于MVar中的putMVar
但readTVar和writeTVar与MVar中的takeMVar和putMVar的最大的差别在于,这里的read和write是没有锁的性质的,即可以连续多次读和写。当然MVar的锁的性质很重要,所以Haskell提供了TMVar类,这个类的takeTMVar和putTMVar函数就和MVar中的takeMVar和putMVar完全等价了。

③STM操作是不能直接执行的,需要用:atomically :: STM a -> IO a 将STM操作转换成IO操作。atomically $ do STM Action。正是因为每个STM操作的运行都要套上atomically,才导致了STM操作都具有①中所讲的原子性。