12.6 实现选项的计算表达式

12.6 实现选项的计算表达式

 

    在 12.4 节中,我们用选项值作为示例,介绍了用 LINQ 查询和 F# 的计算表达式创建非标准计算的概念。我们所写的代码处理选项值,有自定义的值绑定来读取实际值,就好像是一个标准值。我们已经看到如何转换计算表达式,知道我们的 Bind 成员会接收一个值,和一个 lambda 函数。我们只想执行这个 lambda 表达式,有我们的选项类型计算表达式,如果这个值是 Some(x) 而不是 None 的话。在后一种情况下,我们可以立即返回 None。

    要运行较早的例子,我们需要在 C# 中实现 LINQ 查询运算符,在 F# 中实现选项计算生成器。再次,我们先从 F# 版本开始。清单 12.21 显示了有两个成员的 F# 对象类型。我们已经在第 6 章中实现了 Option.bind 函数,但我们在这里会重新实现它,提醒你典型的 bind 操作做什么。

 

Listing 12.21 Computation builder for option type (F#)

 

type OptionBuilder() =
  member x.Bind(opt, f) =
    match opt with
    | Some(value) -> f(value)
    | _ –> None
  member x.Return(v) = Some(v)

let option = new OptionBuilder()

 

    Bind 成员首先从作为第一个参数值给定的选项中,提取值。这类似于我们前面实现的 ValueWrapper<'T> 类型的 Bind。我们再次使用模式匹配,但在这种情况下,这个值可以省略,所以,我们将使用 match 结构。如果定义了这个值,则调用指定的函数。这意味着,我们将值绑定到使用 let! 声明的符号,然后,运行计算的其余部分。如果未定义这个值,我们将返回 None 作为整个计算表达式的结果。

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

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

 

Listing 12.22 Query operators for option type (C#)

 

static class OptionExtensions {
  public static Option<R> Select<S, R>
      (this Option<S> source, Func<S, R> selector) {
    return source.Map(selector);
  }
  public static Option<R> SelectMany<S, V, R>
      (this Option<S> source,
       Func<S, Option<V>> valueSelector,
       Func<S, V, R> resultSelector) {
    return source.Bind(sourceValue =>
      valueSelector(sourceValue).Map(resultValue =>
        resultSelector(sourceValue, resultValue)));
  }
}

 

    Select 方法应该把给定的函数应用到由给定选项值所拾的值上,如果它包含实际值。然后,它再把这个结果打包到选项类型中。在 F# 中,函数称为 Option.map,我们给这个 C# 方法使用类似的名字(Map)。如果我们第一次看 LINQ,开始可能会调用 Select 方法,但是,最简单的解决方案被添加到新方法 Map 中。

    SelectMany 会更复杂。它类似于 bind 操作,但是,它需要使用额外指定的函数,作为第三个参数值,来格式化操作的结果除外。在第 6 章中,我们写过 C# 版本的 bind 操作,因此,在这个实现中,我们可以使用 Bind 扩展方法。要调用格式化函数 resultSelector,我们需要两个参数值:一个是由选项携带原始值,另一个是由绑定函数 (命名为 selector) 产生的值。我们可以在处理的末尾,添加对 Map 的调用,执行此操作,但是,我们需要把这个调用放在 lambda 函数内部,给 Bind 方法。这是因为,我们还需要访问来自源的原始值。在 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> 类型。

    第一个主要是玩具示例,演示了在实现计算时,我们需要做什么;但是,第二个示例在写组合大量操作的代码时,是有用的,其中每个可能失败。当你分析这两个例子时,可以看到,一元类型是如何的重要。一旦我们理解了类型,就会知道,是什么让计算非标准。

 

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值