12.6 实现选项的计算表达式

在 12.4 节,我们用选项值作为示例,介绍了用 LINQ 查询和 F# 计算表达式创建非标准计算的概念,处理选项值的代码,有自定义的值绑定读取实际值,如同标准值。既然我们已经知道如何转换计算表达式,也就知道我们的 Bind 成员会接收值和 lambda 函数。因为我们处理的是选项类型计算表达式,只有当值是 Some(x) 而不是 None 时,我们才打算执行 lambda 表达式;后一种情况,我们可以立即返回 None。

要运行前面的例子,我们需要在 C# 中实现 LINQ 查询运算符,在 F# 中实现选项计算生成器。同样,我们先看 F# 版本,清单 12.21 是有两个成员的 F# 对象类型。我们已经在第六章实现过 Option.bind 函数,在这里要重新实现,提醒一下典型的 bind 操作做什么。

清单12.21 选项类型的计算生成器 (F#)

type OptionBuilder() =

member x.Bind(opt, f) =

match opt with       [1]

| Some(value) ->f(value)    [2]

| _ –> None    [3]

member x.Return(v) = Some(v)   <-- 包装实际值

let option = new OptionBuilder()

Bind 成员首先从第一个参数的选项值中,提取值,这类似于我们前面实现的 ValueWrapper<‘T> 类型的 Bind。同样,我们使用模式匹配[1],但在这里,值可以省略,所以,我们将使用 match 结构。如果定义了这个值,则调用指定的函数[2]。这样,我们就把值绑定到使用 let! 声明的符号上,然后,运行计算的其余部分。如果未定义这个值,就返回 None,作为整个计算表达式的结果[3]。

Return 成员有一个参数值,必须返回计算类型的值。在我们的示例中,计算类型是 option<‘a>,所以,把实际值包装到 Some 识别器内。

为了在 C# 中使用查询语法写对应的代码,就需要到在第五章为定义 Option<T> 类型实现的 Select 和 SelectMany 方法。清单 12.22 实现了两个额外的扩展方法,这样,就可以在查询表达式中使用选项了,这次,我们使用在第六章中写的扩展方法,使代码更加简单。

清单12.22 选项类型的查询操作 (C#)

static class OptionExtensions {

public static Option<R>Select<S, R>

(thisOption<S> source, Func<S, R> selector) {

returnsource.Map(selector);    [1]

}

public static Option<R>SelectMany<S, V, R>

(thisOption<S> source,

Func<S, Option<V>> valueSelector,

Func<S, V, R> resultSelector) {

returnsource.Bind(sourceValue =>    [2]

valueSelector(sourceValue).Map(resultValue =>    [3]

resultSelector(sourceValue, resultValue)));

}

}

如果给定选项值包含实际值,Select 方法应该把给定的函数应用到所携带的值上,然后,再把结果打包到选项类型中。在 F# 中,函数称为 Option.map,C# 方法也使用类似的名字(Map)。如果我们是先看到 LINQ 的话,那么,首先可能会把方法称为Select,但是,最简单的解决方案是添加新的称为Map 方法[1]。

SelectMany 会更复杂,它类似于 bind 操作,但是,此外需要使用由第三个参数,指定额外的函数,格式化操作的结果。在第六章,我们用C# 写过 bind 操作;在这里,我们可以使用 Bind 扩展方法[2]。要调用格式化函数 resultSelector,需要两个参数:一个是由选项携带原始值,另一个是由绑定函数(命名为 selector)产生的值。在处理的末尾,我们可以添加对 Map 的调用执行此操作,但是,需要把这个调用放在 lambda 函数内部,给 Bind 方法[3],这是因为我们还需要访问来自源的原始值。在
lambda 函数内部,原始值是作用域之内(名为 sourceValue 的变量),因此,我们可以把它和新值,即,分配给变量 resultValue,放在一起使用。

这个实现是有点复杂,但它表明,使用函数式编程,可以把很多已有的功能组合起来。如果我们试图自己实现的话,在这里,可以看到类型是宝贵的助手。可能首先使用 Bind 方法,但是,可能看到类型不匹配。会看到什么类型不兼容,如果看过哪些函数可用,你会发现,为了获得正确的类型,需要添加什么。我们自己重复的风险在于:函数式编程中的类型要重要得多,告诉你更多有关程序的正确性问题。

使用新的扩展方法,我们可以运行 12.3 节中的示例。在 F# 中,我们不需要实现 yield 和 for 基本操作,只使用 return 和 let! 就能运行。这是故意的,因为这些第一组基本操作更适合处理各种形式序列的计算。我们仍然需要实现 TryReadInt 方法(类似于 F# 的函数),这些是简单的,因为只要从控制台读取一行,并尝试解析,然后,当字符串是数字时,返回 Some,其它则返回 None。

肯定和可能单子

我们前面看到的两个例子,在 Haskell 中是众所周知的。第一个示例是肯定单子(identity monad),因为这个单子类型与值的实际类型相同,只打包在命名类型中。第二个示例是可能单(maybe monad),因为Maybe 是 Haskell 的类型名,对应于 F# 中的 option<‘a> 类型。

第一个示例没有什么实际意义,演示了在实现计算时需要做的;而第二个示例在写组合大量操作的代码时,其中每个可能失败,是有用的。我们分析这两个例子时,可以发现,单子类型非常重要;一旦我们理解了这种类型,就会知道,是什么让计算非标准。

到目前为止,例子已经有点抽象了;下一节,会有很多更具体的示例,在代码中添加自动日志。

时间: 2024-10-18 10:23:13

12.6 实现选项的计算表达式的相关文章

12.7.3 使用计算表达式进行重构

在前一章,我们讨论过重构函数式程序的方法,最后一个主题是延迟性,它变改代码的执行方式,而不影响程序的结果.从某种意义上讲,添加延迟性也可看作是一种重构技术:计算表达式的类似之处在于,增加额外的代码,但不改变核心意思. 提示 在计算表达式和延迟性之间有密切的关系,使用 Lazy<'T> 计算类型,创建能够把代码转换成延迟计算的计算表达式,是有可能的.我们可以尝试实现自定义的计算,唯一的难度在于写 Bind 成员.在这里,我们不进行讨论,在本书的网站上可以找到其他的信息. 重要性在于,把标准的 F

PHP学习之-1.4 计算表达式

计算表达式 不同于HTML和CSS,在PHP中做计算,比如我们写 echo 12*3 计算结果是36.代码如下 1 <?php echo 12*3;?> 实例 <!DOCTYPE HTML> <html> <head> <meta http-equiv="Content-Type" content="text/html; charset=utf-8"> <title>计算表达式</titl

12.1.3 使用 F# 序列表达式 在 C# 中的迭代器非常方便(comfortable),能够在普通的 C# 方法中写复杂的代码 (实现 IEnumerable&lt;T&gt;/IEnumerator

12.1.3 使用 F# 序列表达式 在 C# 中的迭代器非常方便(comfortable),能够在普通的 C# 方法中写复杂的代码(实现 IEnumerable<T>/IEnumerator<T> 接口的类型).开发人员写的代码使用标准的C# 功能,比如环,唯一的改变只是我们可以使用一种新的语句,来做一些非标准的事情,这个新语句用 yield return 表示(或者 yield break 表示终止序列),非标准的行为返回序列中下一个元素的值.在以后需要访问序列的时候(最后,计

Justinmind使用教程(2)——计算表达式及条件使用方法

Justinmind的计算表达式以及条件condition的使用对于初学者而言比较麻烦. 结合网上了一个教程本文主要针对计算器示例进行计算表达式以及条件的使用. 实现目标:根据单价(静态)和数量(动态),自动计算出金额.下图: 在数量文本框右侧添加两个矩形部件,上方为+,下方为-,点击+或-自动减1,当数量为0时,不允许计算. [具体步骤] (只介绍重点) 1.选中+:2.添加事件(点击+按钮设置数量+1): 3.事件为"鼠标单击时"(默认):4.选择动作为"设置值"

字符串计算表达式

string str="4+4+2.1"; 要的效果: double sum=4+4+2.1: 方案一: 动态计算表达式: 1 public class Expression 2 { 3 object instance; 4 MethodInfo method; 5 /// <summary> 6 /// 表达试运算 7 /// </summary> 8 /// <param name="expression">表达试</p

题目1101:计算表达式(栈的使用)

题目链接:http://ac.jobdu.com/problem.php?pid=1101 详解链接:https://github.com/zpfbuaa/JobduInCPlusPlus 参考代码: // // 1101 计算表达式.cpp // Jobdu // // Created by PengFei_Zheng on 06/05/2017. // Copyright © 2017 PengFei_Zheng. All rights reserved. // #include <stdi

华为上机练习题--计算表达式

题目: 输入一个表达式,没有括号,数字小于0-9之间,输出计算结果,所有的中间结果化为整形. 例如:  输入:3+8×2/9-2 输出:2 函数原型 public int getMyRet(String str) 分析: 这个题目略显高端啊, 像我这种非专业的自学者,还真没有学过编译原理之类的课程, 要自己实现一个这种小型的编译器是非常困难啊, 所幸的是这个题目是用java来实现的, 而我本身也曾经看到过用java来实现这类的问题, 这类问题有一种方法就是实现类编译器功能的函数, 这个我是驾驭不

Visual Studio断点调试, 无法监视变量, 提示无法计算表达式

在使用Visual Studio 2012进行断点调试时,对某个变量添加监视,出现"无法计算表达式"的提示. 解决办法:依次点击菜单栏中的"调试"→"图形"→"启动诊断" 期间会出现类似如下界面: 公共符号下载完毕,断点调试,变量监视功能恢复正常!

stack计算表达式的值

9.52 使用stack对象处理带圆括号的表达式.遇到左圆括号时,将其标记下来.当你在一个左括号之后遇到右圆括号时,弹出stack对象中这两边括号之间的元素,直到遇到左括号,将左括号也一起弹出栈. 接着在stack对象中压入一个值,用以表明这个用一对圆括号括起来的表达式已经被替换. 程序如下: #include<iostream> #include<stack> #include<string> using namespace std; int main() { sta