Archive for May, 2009


输球

从第十分钟的时候埃托奥摆脱了F5的防守进球开始,我就意识到这场比赛不会在属于曼联。罗马的天气看上去很潮湿很闷热,看上去更适合是在加泰罗尼亚而不是遥远的英格兰。果然,曼联的最好机会出现在开场1分半时的P13,可惜的是他的身短腿短限制了他腾空的距离,同样的道理验证了下半场失去的一次头球机会。C7在冲刺了一场比赛后,却发现其实他并没有几脚射门的机会,无论是在哪个方面,曼联都被巴萨压在了身下。

首先就是中场的拦截出了极大的问题。巴萨中场三个小个轮番倒球,把曼联的中场弄的找不到了自己。G11明显已经老了跟不上节奏,而A8也就开场第一次那个突破还有点小坦克的意思,其他时候剩下的只是黑辫子在那荡啊荡的。而C16虽然已经很努力,但是他一个人怎么可能扛得过三个小个。缺少了F24的曼联中场,碰到了这种技术流派的打发,吃亏到了极点。而在两个边路,无论是P13还是R9都没有任何发挥,因为球根本到不了他们那里。P13属于只能铲球断球类型,R9也只属于那种突一下拼速度的,于是两个人在边路频繁的传球冲刺,接着频繁的带球。曼联唯一一个可以带球的C7,一个人孤零零地站在最前面,被P5缠着心情也是坏到极点。

曼联要想重新拿回冠军,人员的调整是必不可少的。虽说目前的人员配备已经很充分,但是其实隐患还是不少,特别是在中前场。将C7一个人放在前面而过分依靠他是很危险的做法,即使他在鼎盛时期也没有比如如此地指望他。要知道以前C7可是只在右路埋伏着的,现在R9因此也埋没了整个赛季的光辉,出彩的都给了C7。如果可行的话,下赛季把T32留下,把C7就卖到皇马,换几个不错的再赚一笔。现有的人才还是很不错的。而G11,S18几位老将们今后出场的机会可能也越来越小了。

现在F估计在后悔当初不应该在对EVERTON的比赛中派替补出场了,不然至少这个星期六还有可能在温布利去争夺另一个冠军。

Genomics复习(4) — DNA序列比较2

1. BLAST是一种局部比对的搜索工具,采用了Heuristic方法。包括BLASTN, BLAST2, BLASTP, PSI-BLAST, TBLASTN…..
BLAST找到潜在匹配的种子:数据库索引话,将长度为W的字符拼版话,在数据库中寻找匹配(DNA-完全匹配,蛋白质-找到比阈值高的);扩展潜在匹配;评估潜在匹配;检测和评估最后的匹配:利用Smith-Waterman局部比对算法,决定匹配的数据重要性

2. 低复杂区域:
基因组中包括低复杂区域,如重复的小片段(引物),高A-T区域(或其他有线的碱基),这些区域的匹配通常不明显,最好能在进行BLAST处理前屏蔽掉。
SEG:一种检测低信息区域的算法,利用信息理论,参数决定了粒度和力度。
1)检测低复杂区域,2)局部优化,尖锐边界

3. BLAST输出分数
S:DPA查询分数
Z:随机化查询序列多次,利用不同的比对
P:一个或多个序列的分数>=S的几率,p可以通过z计算出来
E:期望的序列分数>=S的数目,E=P*数据库的大小

4. Dotplots
比较两个序列,滑动窗口W,阈值T,比较窗口内的字符,计算窗口内的匹配数目,如果数目大于阈值则加上一个点

5. 多序列比对
打分方法:SP-成对数目,一致性(星形):一个序列是其他序列的祖先,信息理论:最小熵,树形(渐进比对),图形

Genomics复习(3) — DNA序列比较1

1. DNA, RNA和蛋白质序列都可以被看作为字符串,字符串的搜索算法有:Horspool/Boyer-Moore. KMP, Rabin-Karp, Suffix-trees。和生物学有关的方面包括:近似匹配,因为测序错误或者进化(在物种内部或之间)。我们需要寻找的是相似的字符串,部分匹配,能够达到近似的测量值。

2. 测量两个序列之间的距离:Euclidean距离,City Block距离,Hanmming距离
Edit距离是最小的操作数目来吧一个字符串转换为另一个字符串,操作包括插入/删除,和替换
优点:对生物进化的建模,考虑了序列相关的可能性;缺点:可能与实际的进化过程不符合,通常情况下两个相关的DNA片段是继承自同一祖先,而不是相互。

3. 序列比对
全局比对:插入空格,在中间或者序列1和序列2的结尾;将一个序列放在另一个序列的上方,使得每个字符都是另一个字符对应的或者是空格。
DPA算法:动态编程来计算出比对的最小值,两步:计算通过递归关系得到距离,回溯。
代表算法:Needleman-Wunsch算法
局部比对,代表算法:Smith-Waterman算法

Genomics复习(2) — 基因组组织

困难:
RNA拼接:基因内区和外显子
开始和结束信号
非翻译区域:5UTR,3UTR
控制信号
结构化DNA
重复的区域,基因和伪基因
DNA包装影响基因表达

人类基因组:约有3*10pow9个碱基对

基因组VS后基因组时期:
2001年之前属于基因组时期:大多数的目标是测序
2001年之后属于后基因组时期:重要的目标包括分析和更有效的测序

DNA测序:传统和现代方法
传统:Sanger方法
工作流为:提纯基因组片段,扩大增强片段,测序片段,集合片段(集合片段是最难的部分)
将DNA片段裂解为单股,
现代:454方法,Shendure and Hanlee方法

下一代测序方法:
综合测序包括许多成像,需要耗费很多时间,数据管理问题,昂贵的原始数据归档费用,一般很难完成
对读取长度很小的算法需要花费相当的计算来集合

在基因组测序中的基本问题:
只能测序小片段,重复部分很困难,数据中有错误
问题1:大小分别
人类的基因组总数为3*10pow9个碱基,而Sanger方法只能测序500-1000碱基,下一代测序方法让不同更为明显,454测序只能250个碱基而其他的只能25-40个碱基
基因集成方法:层次集合,Shotgun测序
问题2:重复区域
30%的人类基因组是重复的,重复使得集成十分困难,散布的重复:SINES和LINES,串联重复
重复是近似的
问题3:错误
湿实验室更容易造成错误,如果序列的比较是近似的,它是重复区域?还是实验室错误?处理中的错误。

集成中的算法考虑:
错误,重复,准确估计,方向,覆盖率,不同片段的连接,效率

Genomics复习(1) — 大分子信息

生物信息学的话题概述:
1) 大分子信息
2) 基因组的组织和表达
3) 基因组信息(数据库)的获得
4) DNA序列,结构,分析
5) RNA序列,结构,分析
6) 蛋白质和蛋白质家族

从计算机科学的角度,DNA是一个由4个字符组成的字符串,ACTGGTCAA……
DNA的基础:
ACGT四种碱基,A-Adenine腺嘌呤,C-Cytosine胞嘧啶,G-Guanine鸟嘌呤,T-Thymine胸腺嘧啶,是一种含氮的环状结构。
核苷是由碱基加上糖组成,糖分为脱氧核糖deoxyrubose和核糖ribose两种,前者出现在DNA中,后者出现在RNA中。
核苷酸是由核苷加上磷酸盐组成。
在RNA中,胸腺嘧啶T被尿苷Uridine取代。
DNA是一种双螺旋结构:
嘧啶Pyrimidines:较小,包括T和C, 嘌呤Purines:较大,包括A和G
两两配对,A-T(2根氢键,较弱),G-c(3根氢键,较强)

命名系统Nomenclature:扩展的核苷酸字符
单核苷酸:{A},{C},{G},{T}
任何:N={A,C,G,T}
嘌呤,嘧啶:Y={C,T}, R={A,G}
弱键/强键:W={A,T}, R={C,G}
氨基Amino/酮类keto:M={A,C}, K={G,T}
V={A,C,G}, H={A,C,T}, D={A,G,T}, B={C,G,T}
标准的序列如:TTAAACNGCSNTTT
字符表的大小为2pow4-1=15

DNA结构:
主结构:序列型,有方向的
次结构:沃森-克里克Watson-Crick双螺旋模型
高阶结构:三维超螺旋结构

DNA信息流,DNA是蓝图,蛋白质负责工作
DNA->RNA 转录, RNA->Protein 翻译
每一部都有预处理,还包括一些控制:
蛋白质控制:催化剂,抑制剂,调制器
级串联,暂时控制等等

基因和蛋白质:
基因是一系列的DNA用来编码蛋白质的,蛋白质做了大部分细胞内的工作
一个蛋白质内有3个DNA碱基
基因组有很多基因(已经被解码的),还有很多没有被翻译的片段

DNA信息:
DNA是蓝图,人类的DNA大约有3*10pow9个碱基,大部分(>90%)还没有被编码,大部分的 DNA(>30%)是重复的,展开大概有1米厂,组成部分随着不同的器官和物种而不同。

压缩:
压缩是一种将信息尽量精炼的办法。它包括:模型和方法。有些时候模型是十分有用的,即使没有编码。无损耗的压缩支持精确的原始序列还原。

模型和熵:
DNA的模型包括:
E.coli: p(T) = p(C) = p(A) = p(G) = 0.25, G+C=50%, 每一种碱基都是同等可能性
P.falciparum: p(A) = p(T) = 0.4, p(C) = p(G) = 0.1, G+C = 20%,不等的碱基比例,信息量小
信息理论允许我们测量混乱和不确定性的程度
熵是用来测量信息的:H=- <!– /* Font Definitions */ @font-face {font-family:宋体; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-alt:SimSun; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} @font-face {font-family:”Cambria Math”; panose-1:2 4 5 3 5 4 6 3 2 4; mso-font-charset:0; mso-generic-font-family:roman; mso-font-pitch:variable; mso-font-signature:-1610611985 1107304683 0 0 159 0;} @font-face {font-family:Calibri; panose-1:2 15 5 2 2 2 4 3 2 4; mso-font-charset:0; mso-generic-font-family:swiss; mso-font-pitch:variable; mso-font-signature:-1610611985 1073750139 0 0 159 0;} @font-face {font-family:”\@宋体”; panose-1:2 1 6 0 3 1 1 1 1 1; mso-font-charset:134; mso-generic-font-family:auto; mso-font-pitch:variable; mso-font-signature:3 135135232 16 0 262145 0;} /* Style Definitions */ p.MsoNormal, li.MsoNormal, div.MsoNormal {mso-style-unhide:no; mso-style-qformat:yes; mso-style-parent:”"; margin:0cm; margin-bottom:.0001pt; text-align:justify; text-justify:inter-ideograph; mso-pagination:none; font-size:10.5pt; mso-bidi-font-size:11.0pt; font-family:”Calibri”,”sans-serif”; mso-ascii-font-family:Calibri; mso-ascii-theme-font:minor-latin; mso-fareast-font-family:宋体; mso-fareast-theme-font:minor-fareast; mso-hansi-font-family:Calibri; mso-hansi-theme-font:minor-latin; mso-bidi-font-family:”Times New Roman”; mso-bidi-theme-font:minor-bidi; mso-font-kerning:1.0pt;} .MsoChpDefault {mso-style-type:export-only; mso-default-props:yes; mso-bidi-font-family:”Times New Roman”; mso-bidi-theme-font:minor-bidi;} .MsoPapDefault {mso-style-type:export-only; text-align:justify; text-justify:inter-ideograph;} /* Page Definitions */ @page {mso-page-border-surround-header:no; mso-page-border-surround-footer:no;} @page Section1 {size:595.3pt 841.9pt; margin:72.0pt 90.0pt 72.0pt 90.0pt; mso-header-margin:42.55pt; mso-footer-margin:49.6pt; mso-paper-source:0; layout-grid:15.6pt;} div.Section1 {page:Section1;} –>

i(Pi * log2pi),当所有的概率均等的时候H的值最大,也就意味着不知道接下来会发生什么。
H(E.coli) = 2.0, H(P.falciparum) = 1.7

编码:常见的三种方法Huffman编码,LZ编码和Arithemetic编码。
1)Huffman编码:
静态编码,利用树的模型,每一个符号都是一个单独的树叶,将最小可能性的两个树叶合并,形成树,然后在剩下的节点中再选择,反复,直到树中的所有节点都是叶子为止。用0表示左,1表示右来区分路径。
缺点:需要给每一个节点赋值,当可能性是2的整数倍时比较好

2) Arithmetic压缩编码
允许存在部分的位,压缩类似于entropy,很难更好

3)LZ编码
适应性的,储存了前面可见字符的移动窗口,当重复足够长的时候指向原来的复件,节省了子字符串的输出成本。
缺点:最小的重复长度来完成压缩可能比模体还要大,在生物中,大部分的重复是近似的,需要更复杂的模型来表示重复。
用处:平均位数和基数在重复区域会下降-因为表示了更少的信息

信息理论在基因组学中:
DNA是由一串字符组成
压缩率(熵)测量的一串字符中的信息
用来:定位重复/相似的序列,相关的基因,相同器官或者伪基因
在比较序列前过滤掉低信息区域
从不同器官中提取DNA
在DNA中找到不同的区域

序列,结构和方法:
所有的大分子都包括序列,结构和方法
生物信息学的很重要的一部分就是找到以上三者之间的联系,从一个预测另外一个,发现关系,接口数据库

RNA结构:
主结构:序列,次结构:2维,第三结构:3维

蛋白质结构:
主结构:序列,次结构:双螺旋,片状,环状,第三结构:3维

猪流感

如果我是猪,我会得猪流感吗?

–墨尔本有四例猪流感患者截止2009年5月21日

Haskell学习(4)-并行和多核计算

因为这章是我这次研究Haskell的重点,因此全文直接翻译。

在我们编写这本书的时候,CPU的架构变化比任何一个时期都要快。

定义并发和并行

一个并发程序需要同时进行多个可能毫无关联的任务。考虑游戏服务器的例子:服务器很普遍的会由多个的部分组成,其中的每一个都与外界有着复杂的交互。一个部分可能负责多用户的交谈,其他的一些部分可能需要处理用户的输入,以及将状态的更新返回给用户;同时其他的一些部分还在进行物理计算。

一个正确运行的并发程序并不需要有多核的支持,尽管有了他们的确可以提高程序的效率和反应。

相反,一个并行的程序解决一个单一问题。考虑经济学模式中常侍预测某一支股票在接下来几分钟的价格波动情况。如果我们需要将这个模型应用到交易中的每一支股票,比如来推断我们需要买哪些卖哪些,将这个模型在五百个核的计算机上运行会比仅仅使用单核的计算机运行要快。通过这个我们可以看到,一个并行的程序并不是经常依靠于多核的出现来保证其正确性。

另外一个有用的并发和并行之间的区别存在于他们与外界的交互之中。从定义来看,并发程序不断的与网络协议,数据库以及相关的联系。一个标准的并行程序可能更加关注于某一点:数据流接入,运算一段时间(伴随很少的I/O),然后将数据流输出。

很多传统的编程语言更加模糊化了并发和并行语言之间的界限,因为他们迫使程序员用同样的基始来构造同样的程序。

在本章中,我们将会关注并发和并行程序执行在以个单一的操作系统进程之中。

通过线程的并发程序

作为并发程序的一个建筑模块,大多数的编程语言都提供了来操作控制多个独立线程的方式。Haskell也不例外,尽管利用线程在Haskell中看上去和其他语言有一些不同。

在Haskell中,一个线程是一个IO行为,独立于其他线程而单独执行的。为了创造一个线程,我们引入Control.Concurrent模块,并且使用forkIO方法。

ghci> :m + Control.Concurrent
ghci> :t forkIO
forkIO :: IO () -> IO ThreadId
ghci> :m + System.Directory
ghci> forkIO (writeFile “xyzzy” “seo craic nua!”) >> doesFileExist “xyzzy”
False

新的线程在程序执行开始的一刹那就开始运行,并且创造新线程的线程同时也在并发执行。新线程直到IO运行结束才停止运行。

线程是非确定行的

GHC的运行时部分并没有明确它执行线程的先后顺序。因此,如同在上面的程序中,新线程创建的文件xyzzy可能,也可能还没来得及新建当原始线程检查它的存在时。如果我们运行这个例子一次,移除掉xyzzy文件后再运行一次,我们可能会得到和第一次运行不一样的结果。

掩盖延迟

假定我们有一个很大的文件需要压缩写入磁盘,但是我们需要很快的处理用户的输入来保证用户可以及时地得到程序的反馈。如果我们使用forkIO来创造一个新线程进行文件的写操作,我们可以同时来完成这两件事。

– file: ch24/Compressor.hs
import Control.Concurrent (forkIO)
import Control.Exception (handle)
import Control.Monad (forever)
import qualified Data.ByteString.Lazy as L
import System.Console.ReadLine (readline)

– Provided by the ‘zlib’ package on http://hacakge.haskell.org/
import Codec.Compression.GZip (compress)

main = do
maybeLine <- readline “Enter a file to compress> ”
case maybeLine of
Nothing -> return ()        — user entered EOF
Just “”    -> return ()        — treat no name as “want to quit”
Just name -> do
handle print $ do
content <- L.readfile name
forkIO (compressFile name content)
return ()
main
where compressFile path = L.writeFile (path ++ “.gz”) . compress

因为我们使用的lazy ByteString I/O,我们所需要做的只是在主线程中打开文件。实际的读文件是在需要的时候才在另一个线程中实现。

handle print的使用给了我们一个简单的方式来打印错误的信息,当用户输入的文件名不存在的时候。

线程之间的简单通信

最简单的让两个线程共享信息的办法是让他们使用同一个变量。在我们的文件压缩的例子之中,主线程同其他线程共享了文件名和文件的内容。因为Haskell的数据默认情况下是不可改变的,这就产生了风险:线程可以改变其他线程对于文件名和内容的值。

我们经常需要线程主动的和其他线程交互。比如,GHC并没有提供方法供一个线程来查找其他的线程是否在运行中,完成了,或者发生了冲突。不过,GHC提供了一种同步变量类型MVar,我们可以用它来达到以上这些目的。

一个MVar变量的行为类似于一个单一元素的盒子:它要么是空的,要么是满的。我们可以把东西放进盒子使它变满,或者把东西拿出使它变空。

ghci> :t putMVar
putMVar :: MVar a -> a -> IO ()
ghci> :t takeMVar
takeMVar :: MVar a -> IO a

如果我们试着将一个值放入已经满的MVar中,我们的线程将会暂时进入休眠状态直到另一个线程把其中的值取出。同样的,如果我们试图从一个空的MVar中取出值的话,我们的线程也将会暂时进入休眠直到另一个线程放入值到其中。

–file: ch24/MVarExample.hs
import Control.Concurrent

communicate = do
m <- newEmptyMVar
forkIO $ do
v <- takeMVar m
putStrLn (”received ” ++ show v)
putStrLn “sending”
putMVar m “wake up!”

newEmptyMVar方法有一个描述性的名字。为了创建一个开始时是空的MVar对象,我们需要使用newMVar。

ghci> :t newEmptyMVar
newEmptyMVar :: IO (MVar a)
ghci> :t newMVar
newMVar :: a -> IO (MVar a)

让我们在ghci中运行程序。

ghci> :load MVarExample
[1 of 1] Compiling Main              (MVarExampole.hs, interpreted)
OK, modules loaded: Main.
ghci> communicate
sending
rece

如果你拥有传统开发语言的并发编程的背景,你可以将MVar对象看作为两个相似的有用的目的。

  • 从一个线程到另一个线程发送消息:比如,一个通知
  • 为一块线程之间共享的不定的数据提供互斥操作。我们将数据放入MVar中当它没有被任何其他线程使用,并且一个线程将其暂时取出并且读或者修改它。

主线程和等待其他线程

GHC的运行时系统对待程序的初始线程和其他线程是不一样的。当主线程完成执行时,运行时系统认为所有的程序都已经结束。如果有任何其它线程还在运行,都将会被终止。

因此,当我们需要有运行时间很长的线程不被终止,我们必须采用一些特殊的安排来保证主线程在其它线程结束之后才完成。让我们开发一个简单的库来简单实现这个功能。

–file: ch24/NiceFork.hs
import Control.Concurrent
import Control.Exception (Exception, try)
import qualified Data.Map as M

data ThreadStatus = Running
|  Finished               — terminated normally
|  Threw Exception  — killed by uncaught exception
deriving (Eq, Show)
– | Create a new thread manager
newManager :: IO ThreadManager

– | Create a new managed thread
forkManaged :: ThreadManager -> IO () -> IO ThreadId

– | Immediately return the status of a managed thread
getStatus :: ThreadManager -> ThreadId -> IO (Maybe ThreadStatus)

– | Block until a specific managed thread terminates
waitFor :: ThreadManager -> ThreadId -> IO (Maybe ThreadStatus)

– | Block until all managed threads terminate
waitAll :: ThreadManager -> IO()

我们使用常用属性来维持ThreadManager抽象类型:将其包装在newType,并且阻止客户端来创造这个类型的值。在引入的模块中,我们列举了类型的构造器和IO方法来构造一个管理器,但是我们并没有导出数据构造器。

–file: ch24/NiceFork.hs
module NiceFork
(
ThreadManager,
,  newManager
,  forkManaged
,  getStatus
,  waitFor
,  waitAll
)  where

对于ThreadManager的实现,我们保存了一个从线程ID到线程状态的映射,将其称之为线程映射。

– file: ch24/NiceFork.hs
newtype ThreadManager =
Mgr (MVar (M.Map ThreadId (MVar ThreadStatus)))
deriving (Eq)

newManager = Mgr ‘fmap’ newMVar M.empty

我们有两层的MVar在这里可以使用,将映射保存在MVar之中。这可以使得对映射的修改成为可能。同时也保证了任何使用这个映射的线程将会得到一致的结果。

为了创造一个线程并且观察它的状态,我们需要一点点的簿记。

– file : ch24/NiceFork.hs
forkManaged (Mgr mgr) body =
modifyMVar mgr $ \m -> do
state <- newEmptyMVar
tid <- forkIO $ do
result <- try body
putMVar state (either Threw (const Finished) result)
return (M.insert tid state m, tid)

安全地修改MVar

在forkManaged中使用的modifyMVar方法是十分有用的:它是takeMVar和putMVar的安全集合。

ghci> :t modifyMVar
ved “wake up!”
modifyMVar :: MVar a -> ( a -> IO (a,b)) -> IO b

它从MVar中取出值,然后传递给一个函数。这个函数即可以生成一个新的值,也可以返回一个结果。如果函数抛出异常,modifyMVar将原来的值重新放回MVar,否则就将新值放入MVar。它返回函数的另一部分作为其结果。

当使用modifyMVar来取代手动用takeMVar和putMVar来操作MVar时,我们需要避免两类常见的并发错误。

  • 忘记将值放回MVar中。这将会使一些线程等待那些永远不可能在MVar中出现的值,从而产生死锁。
  • 错误的考虑异常被抛出的可能性而影响了代码流。这可能会使得程序在不需要的情况下调用putMVar方法从而也导致了死锁。

因为这些安全属性,最好在任何时候都使用modifyMVar。

安全资源管理:一个好的并且简单的办法

我们可以采用modifyMVar的模式,将其应用到许多其他的资源管理情形中去。以下是模式的步骤。

1. 获得一个资源。

2. 将这个资源传递给需要用到它的函数。

3. 一定要释放资源,即使在函数发生异常的情况下。如果真的发生了异常,再次抛出让它能够被程序捕捉到。

除了安全之外,这个方法还有另外一个有点:它使得代码更加简短而容易理解。从上面的forkManaged可以看出,Haskell的匿名函数的轻量级语法使得程序视觉上觉得不突出。

以下是modifyMVar的定义,可以看到针对该模式的形式。

– file: ch24/ModifyMVar.hs
import Control.Concurrent (MVar, putMVar, takeMVar)
import Control.Exception (block, catch, throw, unblock)
import Prelude hiding (catch) — use Control.Exception’s version

modifyMVar :: MVar a -> ( a -> IO ( a,b ) ) -> IO b
modifyMVar m io =
block $ do
a <- takeMVar m
(b,r) <- unblock (io a) ‘catch’ \e ->
putMVar m a >> throw e
putMVar m b
return r

你应该很容易的将这种模式应用到你的需求之中,无论你是在和网络连接,数据库操作,还是C语言库的数据操作。

得到一个线程的状态

getStatus方法能够得到一个线程的状态。如果这个线程不再被管理(或者从来都没有被管理过,它将返回Nothing。

– file: ch24/NiceFork.hs
getStatus (Mgr mgr) tid =
modifyMVar mgr $ \m ->
case M.lookup tid m of
Nothing -> return (m, Nothing)
Just st -> tryTakeMVar st >>= \mst -> case mst of
Nothing -> return (m, Just Running)
Just sth -> return (M.delete tid n, Just sth)

如果该线程仍然在运行之中,程序将返回Just Running。否则,程序将会指出为什么线程被终止了,并且停止管理该线程。

如果tryTakeMVar方法找到了一个空MVar,他将立即返回Nothing而不是阻塞。

ghci> :t tryTakeMVar
tryTakeMVar :: MVar a -> IO (Maybe a)

否则,程序会按常返回MVar中的值。

waitFor方法类似地运行,但是它不会立即返回结果,而是阻塞直到该线程终止为止。

– file: ch24/NiceFork.hs
waitFor (Mgr mgr) tid = do
maybeDone <- modifyMVar mgr $ \m ->
return $ case M.updateLookupWithKey (\_ _ -> Nothing) tid m of
(Nothing, _) -> (m, Nothing)
(done, m’) -> (m’, done)
case maybeDone of
Nothing -> return Nothing
Just st -> Just ‘fmap’ takeMVar st

该方法首先取出拥有线程状态的MVar,如果它存在的话。映射类型的updateLookupWithKey方法是十分有用的:它将查找,修改或者移除值的功能合并在了一起。

ghci> :m +Data.Map
ghci> :t updateLookupWithKey
updateLookupWithKey :: (Ord k) =>
(k -> a -> Maybe a) -> k -> Map k a -> (Maybe a, M)

在这个例子之中,我们总是希望在程序状态给出的时候移除掉MVar,所以线程管理器可以不再管理该线程。如果存在一个值可以提出的话,我们将线程的退出状态从MVar中提出并且去返回它。

–file: ch24/NiceFork.hs
waitAll (Mgr mgr) = modifyMVar mgr elems >>= mapM_ takeMVar
where elems m = return (M.empty, M.elems m)

写出更紧凑的代码

上面的关于waitFor的定义有一点是让人不满意的,因为我们执行了差不多同样的分析在两个地方:在方法内称之为modifyMVar,同样在返回值上。

毫无疑问,可以利用前面的方法来消除这个重复。这个正在讨论的方法就是join,来自于Control.Monad模块。

ghci> :m +Control.Monad
ghci> :t join
join :: (Monad m) => m (m a) -> m a

这里的技巧在于我们可以通过利用第一个情况的返回的IO来处理第二个情况,通过一次运行modifyMVar。我们将会使用join来执行。

–file: ch24/NiceFork.hs
waitFor2 (Mgr mgr) tid =
join . modifyMVar mgr $ \m ->
return $ case M.updateLookupWithKey (\_ _ ->Nothing) tid m of
(Nothing, _) -> (m, return Nothing)
(Just st, m’) -> (m’, Just ‘fmap’ takeMVar st)

这是一个十分有趣的办法:我们可以创造一个monadic方法利用纯代码,然后将其传递直到我们在monad中结束。这是一种灵活的方法来写代码,当我们留心发现它 时候。

Haskell学习(3)–定义类型和函数

1. 定义新的数据类型

很多时候我们需要定义自己所需要的数据类型,在Java中往往是用类来实现的。在Haskell中,我们可以使用data操作符来实现一个新的类型,比如:data BookInfo = Book Int String [String]。其中等号左边的BookInfo表示的是这个类型的名称,等号的右边相当于是Java中的构造函数,只是构造函数的名字可以与类名一致,也可以不同,跟在名字后面的就是参数的类型,在这个例子中,一共有三个参数,分别是Int,String和String数组。

我们还可以定义一个data MagazineInfo = Magazine Int String [String],看上去和前面的参数类型完全一致,但是这已经是两个完全不同的类型了,需要辨别开。

当实现一个类型的实例的时候,可以直接使用myInfo = Book 12345 “Title” ["A.b"]来实现。

另外在定义新类型的时候,直接看到Int,String往往显得不那么直观,如果将其用有意义的名字代替会更加容易理解,这时就可以使用类型别名。如type BookID = Int, type BookName = String, type BookAuthor = [String],这样在定义新的类型的时候,可以直接写为data BookInfo = Book BookID BookName BookAuthor

有的时候,构造函数可以不只有一个,可以有多种方式来实现,比如data BillingInfo = CreditCard CardNumber CardHolder Address | CashDelivery | Invoice CustomerID。在这个类型中,BiliingInfo可以用三种方法来构造,每一种的参数类型都不相同,这样从另一个角度实现了多态。

2. 模式匹配

模式匹配比较类似于函数的调用规范,个人感觉类似于C或者Java中的switch,给定了一些判别方式,如果找到了一致的对象,即向下进行运算。比如sumList (x:xs) = x + sumList xs    sum[]=0。两个表达式定义出了如何计算List的每一个元素的和。在这个匹配过程中,表达式的位置起到了很至关重要的作用,表达式会从上到下开始匹配,一旦找到了匹配的,后面就不在运行。同时,如果一个匹配的都没有找到,那么程序将会报错。

在匹配定义中,如果函数中有些无关重要的参数,可以用_来代替,它代表任意的字符或者类型。

3. 记录语法

在定义类型的时候如果采用了以下的格式去定义:data BookInfo = Book { bookID :: Int, bookTitle :: String, bookAuthor::[String]},那么在使用该类型的实例的时候,可以直接调用得到其属性的值。比如我们生成一个实例book1=Book 12345 “abc” “aaa”,如果使用bookTitle book1将可以得到”abc”,也就是说提供了一种得到变量属性的方式,而这些函数都由Haskell内部自动实现了,你所需要的值是按照这个模式定义新类型既可。

Haskell学习(2)–类型和函数

1. 在Haskell中,所有的表达式和函数都拥有类型,和其他常见的语言一样,类型代表了它所拥有的特性。Haskelld类型归纳起来,可以用三点表示:强类型,静态类型和自动引用。

1) 强类型(Strong types):所谓强类型,是指类型系统保证了程序不会出现特定的类型匹配错误。对于符合语言类型规定的程序我们称之为Well Typed,反之Ill Typed。比如对于一个需要参数为Integer的函数,赋值String即不满足强类型的要求,系统在编译期间将会直接检测出来反馈给用户。
在另一个方面,强类型下系统不支持任何的自动类型转换。比如在C或者JAVA中,有int和double类型的自动转换,但是在Haskell中是不允许的。
强类型给程序会带来一些不方便,但是更多的是他保证了程序在运行之前的完全正确。

2)静态类型(Static types):静态类型是指编译器在编译期间即知道了所有的值和表达式的类型,并确保其正确才运行程序。

3)类型引用(Type inference):指的是Haskell编译器可以自动的推断出大部分表达式的类型而不需要用户主动的去定义。

Haskell的强类型和静态类型确保了其程序不会在运行时发生类型错误,但这也要求了我们在编码的时候需要做更多的前序准备工作。而类型引用则使得整个程序精炼。

2. 一些常见的类型:

char, Bool, Int, Integer, Double

其中Int和Integer的区别在于,Int是长度固定的而Integer可以表示任意大小的数目在系统的允许之下。

3. List和Tuple

区别在于:List用[]表示,它的元素类型是一致的,但是长度是可以变化的;Tuple用()表示,它的元素类型可以是不一样的,但是长度是固定的。同时一般来说,Tuple的元素数量至少是2,称为pair。

对于List和Tuple的一些函数操作包括:

take 2 [1,2,3,4,5] 得到[1,2]    drop 3 [1,2,3,4,5] 得到 [4,5]
fst(1,’a')得到1     snd(1,’a')得到’a'    fst和snd函数只能用于pair。

4. 函数

Haskell的函数类型使用->隔开的,比如:type lines得到的是lines::String->[String]

另外在Haskell中,一旦一个变量被赋予了一个值,是不能去改变这个值的,也就是说同一个变量不能赋值多次。

用If..then…else表示条件语句,需要注意的是else部分必须存在而不能去掉。

Haskell 采用的是Lazy evaluation,也就是说直到需要得到这个值的运算的时候才会去进行计算。比如isOdd n = mod n 2 == 1,在计算isOdd(1+2)的时候,首先不是计算的1+2,而是直接把1+2作为一个整体带入到计算式中,直到必须执行1+2计算的时候才算。

5. Haskell的多态

在 List的last函数中,并没有对函数的参数类型进行规定,也就是说last[1,2,3,4,5]返回的是5,而last “baz”返回的是’z',具体返回的值因参数的类型而定。用:type last查看函数类型,发现是last::[a]->a,其中a是一个类型变量,可以用任何类型来代替,有点类似于Java中的泛型。

Haskell学习(1)

这学期选了一门很变态的课,看上去似乎及其简单,学期的头几周也过得十分愉快,但是到了后面出现了Functional Programming 和 Logical Programing,生活就开始暗淡无光了。于是决定好好从头开始,再把这两套语言粗略的学习一遍。首先从FP开始,主要的总结内容都是摘自Real World Haskell这本书。的确是一本好书,可以再http://book.realworldhaskell.org/read/看到所有的文字,不过是英文。

1. Haskell的开发环境:

Haskell有众多的实现,但是目前来看最为出名的还是Glasgow Haskell Compiler(GHC),基本上大部分的开发都是基于它的。目前的版本最新为6.10.8,而RWH这本书的版本是6.8。还有另外一个版本可以给出,就是Hugs,多用于大学的教学。

GHC包括三个部分:
1) ghc:生成快速本地代码的优化编译器
2) ghci:交互解释器和调试器
3) runghc:不需要首先编译的Haskell运行程序

2. 开始ghci

当运行ghci的时候,可以得到一个Prelude>的提示符。Prelude是一个标准的Haskell库,提供了许多函数可以直接使用。

可以直接使用ghci当作计算器,输入2+2,命令行将会直接返回4,以此类推。此外对于ghci,简单的数学操作可以视为函数运行,因此2+2 也可以写为+ 2 2(Haskell的参数用空格隔开,不需要括号)。但是有一点需要注意的,就是如果在加号中有负数出现,负数最好用括号括起来,不然2+-1就会报错。

Boolean值为True和False,开始的第一个字母大写。&&和||与C语言中一致,并且在Haskell中,没有0就是False,正整数为True的说法。

比较符号,==,>=,<=和C都一样,不同的是不等于写作/=,非写作not。

操作符也是由优先级别的,可以使用:info operator命令来查看操作符的使用方法,优先级别以及是左关联还是右关联。

3. List

List是用[]括起来的一组元素,可以为空[],但是必须元素拥有一样的类型,比如[True, False, "abc"]就会报错。
[1..10]表示从1到10的一个列举,即[1,2,3,4,5,6,7,8,9,10]。如果不指定上界,程序将会不断运行打印知道终止程序位置(Ctrl+C)。同时,列举是依照前两个数据的规律来判断打印的结果的,比如[1,3..10]得到的将会是[1,3,5,7,9]。

对于List的连接操作有两种,++将两个List相连,比如[1,2,3]++[4,5]得到[1,2,3,4,5]。:将一个元素和一个List相连,比如1:[2,3,4]得到[1,2,3,4]。

String字符串被视为List,其元素是一个个的Char。因此String和Char也可以使用以上的两个函数操作。

4. 几个操作符:

1) 当当前提示符过长的时候,可以使用:set prompt将提示符改换为自定义类型。
2) :module + 会装载模块,如:module + Data.Ratio。:m是:module的缩略形式。
3) 当Prelude中没有定义的数据时,可以临时定义,如let e = exp 1,得到e的值,并且后面可以使用e。
4) putStrLn就是打印的作用,类似于Print。
5) :set +t使每次运行都会显示类型信息,:unset +t则取消。:type则会返回参数的类型信息。
6) 可以使用it得到上次运算结果,如果上次运行失败,则it的值不会发生改变。

Older Entries
  • English Version

    • Cannot read Chinese? Please take a look at my English site, hope you can find more you need there!
  • 感谢支持

  • twitter

    facebook

    linkedin

  • Categories