- 浏览: 62462 次
- 来自: ...
最新评论
-
linkerlin:
Monad就这样子的,永久了命令式语言的人,接触Monad都要 ...
抱怨 :: All about Monad -
albertlee:
Real World Haskell 中文版还在娘胎里, 就快 ...
我的学习Haskell经验 -
luyc:
我找到了。你看看能不能下载。
http://www.demon ...
我的学习Haskell经验 -
fv3386:
太感谢了,你的资料真是太棒了
我的学习Haskell经验 -
leeleo:
唉,习惯就好了~~我刚开始上班的时候也是这样,现在就习惯了,心 ...
抱怨 :: All about Monad
5. 插曲:检查错误
目前,在代码的很多地方我们要么忽略了错误,要么静默的指定像#f或者0这种没有任何意义的“默认”值。一些语言 - 像Perl和PHP - 用这种方式工作的不错。但是,它常常意味着那些错误在整个程序里安静的传递知道它们变成大的问题,这说明除错机制对程序员相当不方便。我们希望一旦错误信 号发生它们立刻产生excution。
首先,我们需要导入Control.Monad.Error库来取得Haskell内建错误函数:
import Control.Monad.Error
接下来,我们应该定义一个数据类型来表示错误:
data LispError = NumArgs Integer [LispVal] | TypeMismatch String LispVal | Parser ParseError | BadSpecialForm String LispVal | NotFunction String String | UnboundVar String String | Default String
这是我们目前需要的一些构造符,但是我们也可能会预见其它可能在解释器中出错的事情。下一步,我们定义如何打印出各种错误类型和使LispError成为Show的一个实例:
showError :: LispError -> String showError (UnboundVar message varname) = message ++ ": " ++ varname showError (BadSpecialForm message form) = message ++ ": " ++ show form showError (NotFunction message func) = message ++ ": " ++ show func showError (NumArgs expected found) = "Expected " ++ show expected ++ " args; found values " ++ unwordsList found showError (TypeMismatch expected found) = "Invalid type: expected " ++ expected ++ ", found " ++ show found showError (Parser parseErr) = "Parse error at " ++ show parseErr instance Show LispError where show = showError
我们接下来的一步是让我们的错误类型成为一个Error实例。这对它与GHC内建错误处理函数工作非常关键。开始成为Error的一个实例就意味着它必须提供一个创建一个能从前一个错误或者自身的实例的函数:
instance Error LispError where noMsg = Default "An error has occurred" strMsg = Default
然后我们定义一个类型来表现会抛出一个LispError或者返回一个值的函数。还记得如何用Either数据类型分析来表现异常?我们这里用同样的方法:
type ThrowsError = Either LispError
类型构造符像函数一样结合也能部分应用。一个完整的类型应该是"Either LispError Integer" 或是"Either LispError LispVal",但是我们想表达成"ThrowsError LispVal" 等等。我们值部分应用Either给LispVal,创建了一个我们可以用在任意数据类型的类型构造符。
Either也是另一个monad实例。在这里,在Either动作中传递的“附加信息”是是否一个错误发生了。绑定在函数上的是Either动作有一个 正常值,或者无须计算直接传递一个错误。这是异常如何在其它语言中工作,但由于Haskell是惰性计算的,这里不需要一个分开的流程控制结构。如果绑定 判断这个值已经是一个错误,那么函数永远不会被调用。
Either monad也提供了两个额外的函数:
- throwError,它会取一个错误值并提升它进Either的左(错误)构造符
- catchError,它会取一个Either动作和一个函数并将一个错误转换成另一个Either动作。如果这个动作表示一个错误,它应用给其它函数,你可以这样使用:用return将错误值转换成正常值或者重新抛出另一个错误。
在我们的程序里,我们将转换所有的错误成为它们的字符串表现形式然后作为正常值返回。让我们创建一个帮助函数来为我们做这件事:
trapError action = catchError action (return . show)
调用trapError的结果是另一个Either动作,它会总是返回有效(右边的)数据。我们仍然想将数据从Either monad中解开,这样它就能传递给其它函数:
extractValue :: ThrowsError a -> a extractValue (Right val) = val
我们故意让extractValue没有定义Left构造符,因为这个表示一个程序错误。我们只希望在catchError后使用extractValue,所以它最好在将坏数据注入其它程序前出错。
现在我们有所有的基础结构,是时候开始使用我们的处理错误函数。还记住我们的分析器之前在出错时仅返回一个“No match”字符串吗?让我们修改它,让它包装和抛出原始的ParseError:
readExpr :: String -> ThrowsError LispVal readExpr input = case parse parseExpr "lisp" input of Left err -> throwError $ Parser err Right val -> return val
这里,我们首先用LispError构造符Parser来包装原始的ParseError,然后用内建函数throwError来返回ThrowsError monad中的值。因为readExpr现在返回一个monadic 值,我们需要将其它返回函数的值包装。
下一步,我们改变eval函数的类型签名来返回一个monadic值,相应的调整返回值,添加一个当我们遇见我们无法识别时抛出错误的分句:
eval :: LispVal -> ThrowsError LispVal eval val@(String _) = return val eval val@(Number _) = return val eval val@(Bool _) = return val eval (List [Atom "quote", val]) = return val eval (List (Atom func : args)) = mapM eval args >>= apply func eval badForm = throwError $ BadSpecialForm "Unrecognized special form" badForm
因为函数应用语句递归调用eval(现在返回一个monadic值),我们需要改变它。首先, 我们需要改变map为mapM,它映射一个monadic函数给一个列表,用bind将动作结果按顺序排列,最后返回里值的列表。在Error monad里面,按顺序执行所有的计算但是如果任意出错会抛出一个错误值-成功时返回Right [result],或者失败时返回Left error。接下来,我们用monadic "bind"操作符来将结果传入部分应用的"apply func",再次当任何操作失败时返回错误。
下一步,我们改变apply自身让它当不识别函数时抛出一个错误:
apply :: String -> [LispVal] -> ThrowsError LispVal apply func args = maybe (throwError $ NotFunction "Unrecognized primitive function args" func) ($ args) (lookup func primitives)
我们没有添加一个返回状态给函数($ args)。我们正要改变我们的原始函数,使从lookup中返回的函数能够返回ThrowError动作:
primitives :: [(String, [LispVal] -> ThrowsError LispVal)]
当然,我们需要改变numericBinop函数实现这些原始函数让它在只有一个参数时抛出错误:
numericBinop :: (Integer -> Integer -> Integer) -> [LispVal] -> ThrowsError LispVal numericBinop op singleVal@[_] = throwError $ NumArgs 2 singleVal numericBinop op params = mapM unpackNum params >>= return . Number . foldl1 op
为报告错误,我们用一个at模式来捕捉单值的情况将实际值传入。这里我们寻找一个只有一个元素 的列表,而且我们不想关心它是什么元素。我们也需要使用mapM来按顺序排好unpackNum的结果,因为每一个独立的unpackNum调用可能会因 TypeMismatch出错:
unpackNum :: LispVal -> ThrowsError Integer unpackNum (Number n) = return n unpackNum (String n) = let parsed = reads n in if null parsed then throwError $ TypeMismatch "number" $ String n else return $ fst $ parsed !!0 unpackNum (List [n]) = unpackNum n unpackNum notNum = throwError $ TypeMismatch "number" notNum
最后,我们需要改变我们的主函数使用这整个error monad。这会有一点复杂,因为现在我们正和两个monad(Error 和 IO)打交道。作为结果,我们回到do-notation,因为这几乎不可能当结果是一个monad嵌套在另一个里面时用point-free风格:
main :: IO () main = do args <- getArgs evaled <- return $ liftM show $ readExpr (args !!0) >>= eval putStrLn $ extractValue $ trapError evaled
这是这个新函数干的事情:
- args是一个命令行参数列表
- evaled是这些的结果:
- 取出第一个参数 (args !!0)
- 分析它 (readExpr)
- 将结果传给eval(>>= eval; 绑定操作符比函数有更高的优先级)
- 在Error monad中调用show。
- caught是这些的结果:
- 在求值后调用trapError,将错误转换成为字符串表现形式。
- 调用extractValue来从Either LispError String动作中取出字符串
- 用putStrLn打印结果
编译并运行新代码,然后尝试抛出一些错误:
jdtang@debian:~/haskell_tutorial/code$ ghc -package parsec -o errorcheck listing5.hs jdtang@debian:~/haskell_tutorial/code$ ./errorcheck "(+ 2 \"two\")" Invalid type: expected number, found "two" jdtang@debian:~/haskell_tutorial/code$ ./errorcheck "(+ 2)" Expected 2 args; found values 2 jdtang@debian:~/haskell_tutorial/code$ ./errorcheck "(what?2)" Unrecognized primitive function args: "what?"
一些读者反应你需要添加--make标志来建立这个例子,和后面的一些例子。这个标志告诉GHC建立一个完整可执行的程序,搜索出所有在导入声明中列出的依赖。上面的命令在我的系统里工作正常,但是如果在你的系统失败,用--make试试。
发表评论
-
关于Kibro
2009-04-14 20:25 839这是一个灵活性很高的fastcgi框架 kibro - ... -
CPS
2009-04-13 22:27 730http://library.readscheme.org/p ... -
潜心修炼
2009-04-12 09:01 950看了很多,感觉已经知道monad是咋个回事儿了,面对却胆怯。 ... -
Write Yourself a Scheme in 48 Hours(4)
2009-04-09 21:10 13584. 求值,第一部分 4.1 开始求值 现在,我们仅仅 ... -
Parsec3
2009-04-07 21:15 1044Parsec3和它的上一个版本变了不少,官方说法是应用范围更 ... -
Write Yourself a Scheme in 48 Hours(3)
2009-04-07 10:53 26723. 语法分析 3.1 :写一个简单的分析程序 现在, ... -
Write Yourself a Scheme in 48 Hours(2)
2009-04-06 09:36 17392. 第一步 首先,你需要安装 GHC 。在 Lin ... -
Write Yourself a Scheme in 48 Hours(1)
2009-04-05 08:56 2840PRE:Write Yourself a Scheme i ... -
抱怨 :: All about Monad
2009-04-04 20:42 1356代码写得太BT,恶心死了 例: convert :: ... -
我的学习Haskell经验
2008-11-22 22:10 3927才学了一个月不到,谈不上太多的经验 1、现明白abst ... -
谈谈Haskell的抽象
2008-11-22 22:01 806数据抽象 原子 Tuples && Lists ... -
Iterate abstract --Basic
2008-11-21 20:34 881不存在“无中生有”,物理学家寻找基本粒子,我们即是上帝,数学家 ... -
Iterate abstract --Prelude
2008-11-21 19:55 794熟悉Haskell的同志对这个标题很熟悉,iterate Pr ... -
为什么要学FP
2008-11-21 12:33 9331、抽象和模块化大幅提高生产力 2、FP中函数是First-c ... -
Wearing the hair shirt: a retrospective on Haskell
2008-11-20 23:10 873写道 Simon Peyton Jones 在 POPL 2 ... -
Monad 资源
2008-11-20 21:51 7891. A video of monad lectures on ... -
Show and Read
2008-11-19 09:15 736还不是特别明白,但是已经可以工作了。 附件是一个讲义 -
Haskell 资源
2008-11-18 21:31 1703Haskell Wiki -- Haskell 相关的各种信息 ... -
Haskell 学习记录
2008-11-17 16:12 1218大概去年的这个时候,我听说了函数式编程这个词儿,一头雾水。大概 ...
相关推荐
Write a Scheme interpreter in Haskell step by step.
《Teach Yourself Scheme in Fixnum Days》是一本关于Scheme编程语言的自学教程,本书内容涵盖了从基础到高级的多个知识点,致力于让读者在有限的天数内掌握Scheme编程。在进行知识点梳理之前,我们先对文档内容进行...
《Teach Yourself Scheme in Fixnum Days》是一本由Dorai Sitaram编写的经典书籍,旨在教授读者如何在有限的时间内掌握Scheme编程语言。Scheme是Lisp家族的一种方言,以其简洁性和灵活性而著称,是计算机科学教育和...
《Teach Yourself Scheme in Fixnum Days》是一本详尽的教程,旨在帮助读者在有限的时间内掌握Scheme语言的基础及进阶知识。此书由Dorai Sitaram撰写,并且在网络上部分中文翻译已经存在(参考链接:...
Chapters are organized as a series of groups, or "layers," each of which advances the reader to a new level in Scheme. The first layer (chapters 2-7) introduces Scheme procedures - how to define, use,...
编写自己的方案无编译器错误“为自己编写一个计划”教程,其中包含一些微不足道的修改。 涉及 Read 类型类的错误非常令人分心; 我已经在listing7.hs、listing8.hs 等中留下了关于重叠模式匹配的警告,因为它只是一...
scheme语言相关的学习资料: guide_racket_scheme.pdf Lisp之根源.pdf Racket图文教程.pdf scheme-primer.pdf ...The_Little_Schemer.pdf ...Write_Yourself_a_Scheme_in_48_Hours.pdf The+Seasoned+Schemer.pdf
标题 "write-yourself-a-scheme:48小时内为自己编写计划的游乐场" 指的是一项挑战,旨在引导参与者在48小时内学习并实现Scheme语言的一个简易解释器。这是一个编程项目,通过它,开发者可以深入理解编程语言的内部...
"A Preprocessing Scheme for High-Cardinality Categorical Attributes"这个主题探讨的就是如何有效地处理这类问题。 一、高基数类别变量的挑战 1. 维度灾难:高基数会导致数据的维度增加,这可能会引起过拟合,...
(How to Write a (Lisp) Interpreter (in Python))和(An ((Even Better) Lisp) Interpreter (in Python))的翻译,对解释器实现原理和函数式编程敢兴趣的可以下载看看!
### 一种非采样轮廓域中的新型彩色图像水印方案 #### 概述 本文介绍了一种基于非采样轮廓变换(NSCT)和支持向量回归(SVR)技术的新型彩色图像水印算法。该算法针对几何畸变攻击具有良好的抵抗能力,能够在保持...
This thoroughly updated edition of The Scheme Programming Language provides an introduction to Scheme and a definitive reference for standard Scheme, presented in a clear and concise manner....
scheme语言的解释器scheme48
标题 "scheme_in_forth.tar_in_scheme_forth_源码" 提供的信息表明,这是一个关于用Forth语言实现Scheme解释器的项目。Forth是一种结构简单、效率高的编程语言,而Scheme是Lisp家族的一种方言,以其简洁的语法和强大...
### Fluent Scheme Programming 英文版知识点详述 #### 标题与描述中的核心知识点解析 **标题**: "Fluent Scheme Programming English Version" 该标题表明这是一篇关于如何使用Scheme编程语言来增强ANSYS Fluent...
针对802.11n的聚合机制,提出一种解决方法:In this paper, we proposed an A-MSDU frame aggregation with sub-frame integrity check and retransmission at the MSDU level without altering the original MAC ...
《A Tour of Scheme in Gambit》是一份详尽的指南,旨在介绍如何在Gambit-C解释器中使用Scheme语言进行编程。这份文档由Mikael More撰写,并且允许免费无限传播,前提是保留原有的版权信息。文档提供了PDF、HTML和...