问题描述
language suggestion指出,优点在链接的文件中有详细说明。我快速浏览了一下,但看不到它的清楚说明。
每个语句都可以并行执行是否有优势,这样我可以提高速度?
还是有某种逻辑可以满足您的需求,而使用通常的单子let!
不方便吗?
我了解到,这具有适用性,这意味着它具有以下限制:我无法使用先前的表达式来确定后续表达式的逻辑。那是否意味着要权衡灵活性才能提高效率?
解决方法
一段时间以前,我对Don Syme's description的理解是,let! ... and! ...
链中的每一步都会执行,这与您使用let! ... let! ...
的情况不同。例如,假设您使用option
CE。那你写
option {
let! a = parseInt("a")
let! b = parseInt("b")
return a + b
}
仅执行第一个let!
,因为一旦遇到无,CE就会短路。写let! a ... and! b = ...
会尝试解析两个字符串。据我了解,虽然不一定是并行的。
当从外部来源解析数据时,我看到了一个巨大的好处。考虑实例运行
parse {
let! name = tryParse<Name>("John Doe")
and! email = tryParse<EMail>("johndoe@")
and! age = tryParse<uint>("abc")
return (name,email,age)
}
并得到一个Error(["'johndoe@' isn't a valid e-mail"; "'abc' isn't a valid uint"])
作为回报,而不只是第一个错误。对我来说很整洁。
我为此感到兴奋,有两个原因。潜在的更好的性能,以及某些Monad模式无法适应的特性可以适应Applicative Functor模式。
为了支持已经存在的let!
,需要实现Bind
和Return
,即Monad模式。
let!
和and!
需要Apply
和Pure
(不确定确切的F#名称),即应用仿函数模式。
应用函子的功能不如Monads强,但仍然非常有用。例如,在解析器中。
正如菲利普·卡特(Philip Carter)所提到的,可以将Applicative Functor比Monads效率更高,因为它可以缓存它们。
原因是Bind
:M<'T> -> ('T -> M<'U>) -> M<'U>
的签名。
实现绑定的一种典型方法是“执行”第一个参数以产生值'T
,并将第二个参数传递给它以获取下一步执行的M<'U>
。
第二个参数是一个可以返回任何M<'U>
的函数,这意味着无法进行缓存。
对于Apply
,签名为:M<'T -> 'U> -> M<'T> -> M<'U>
。由于这两个输入都不是函数,因此您可以将其缓存或进行预计算。
这对于解析器很有用,例如出于性能原因在FParsec
中不建议使用Bind
。
此外; Bind
的“缺点”是,如果第一个输入没有产生值'T
,则无法获得第二个计算M<'U>
。如果您要构建某种类型的验证器monad,则意味着一旦由于输入无效而无法生成有效值,验证就会停止,并且您将无法获得剩余输入的报告。
使用Applicative Functor,您可以执行所有验证器以生成整个文档的验证报告。
所以我认为这很酷!