functional programming
1.
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
4.
5.
6.
7.
8.
这里用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.
2.
3.
4.
5.
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)
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操作
运行程序后,首先程序在getLine等你输入,输入后会把你输入的值传给setReminder函数,获取一个IO ()类型,而forkIO函数接收一个IO ()类型,并会另开一个线程去执行IO ()这个IO操作,然后因为forever的缘故(就把它当无限循环就好了),然后支线程开好后,就又回到getLine等待输入。
SetReminder中的内容就是在支线程中要执行的IO操作,do下的每一行都是一个IO Action,合并成了一个大的IO Action返回(这就是Monad在起作用,它将多个函数合成一个大函数),它会打印出提示内容,然后等若干秒,再打印出一串内容
线程之间并行运行,支线程计算时间,主线程可以继续接收用户输入去开启下一个线程
19. 线程间的通信:MVar
首先执行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操作都具有①中所讲的原子性。