15.3.3_将函数转换成“行为函数”

15.3.3 将函数转换成“行为函数”

 

    Behavior.map 函数取两个参数,我们在清单 15.8 中指定了。偏函数应用可以用一个参数值来调用这个函数。以这种方式使用函数,会给我们一个有趣的见解。在清单 15.9 中,我们只指定第一个参数值 (函数);我们将使用 abs 函数,返回一个整数的绝对值。

 

Listing 15.9 Using Behavior.map with partial application (F# Interactive)

 

> abs;;
val it : (int -> int)

> let absB = Behavior.map abs;;
val absB : (Behavior<int> -&gt; Behavior<int>)

 

    第一行显示了 abs 函数类型,第二行显示,如果我们调用 Behavior.map 函数,用 abs 作为第一个参数值,也是唯一的参数值时,会发生什么。结果的类型是一个函数,取 Behavior<int>,返回 Behavior<int>。这意味着,我们用 Behavior.map 来创建一个函数,计算行为值的绝对值。我们可以使用这种技巧,把任何取一个参数函数转换成另一个函数,对这个行为做同样的事。

 

运算符和函数的提升(lifting)

 

    刚才我们看到构造是在函数式编程中一个著名的概念,通常称为提升(lifting)。在某种程度上,我们可以甚至称之为函数式的设计模式。Haskell wiki [HaskellWiki,2009] 对提升有一个定义,可以把处理值的函数转换成另外的函数,做同样的工作,以不同的设置。提升采取的是 C# 2.0 语言的功能,所以,我们可以使用熟悉的代码来演示。如果我们想创建一个基元值,比如 int,它可以有 null 值,可以使用 C# 2.0 可空类型:

 

int? num1 = 14;
int? num2 = null;

 

    到目前为止,没有什么不同凡响的。我们声明了两个可空的 int 值,一个包含一个真正的整数值,另一个没有值。你不可能知道可以像这样写:

 

int? sum1 = num1 + num2;
int? sum2 = num1 + num1;

 

    第一个计算的结果是 null,因为至少有一个参数值是 null。第二个表达式的结果是 28,因为,运算符 + 的两个参数值都有值。在此示例中,C# 编译器取 + 运算符,处理整数,并创建提升的 + 运算符,能够处理可空的类型。这个操作类似于我们行为所要做的。

    Behavior.map 为有一个参数的函数实现了提升,但是,我们想要为其他函数实现相同的功能。清单 15.10 显示了几个辅助函数,允许有最多三个参数提升的函数。

 

Listing 15.10 Lifting functions of multiple arguments (F#)

 

&gt; let lift1 f behavior =
     map f behavior
   let lift2 f (BehaviorFunc bf1) (BehaviorFunc bf2) =
     sample(fun t -&gt; f (bf1(t)) (bf2(t)))
   let lift3 f (BehaviorFunc bf1) (BehaviorFunc bf2) (BehaviorFunc bf3) =
sample(fun t -&gt; f (bf1(t)) (bf2(t)) (bf3(t)))
;;
val lift1 : ('a -&gt; 'b) -&gt; B<'a> -&gt; B<'b>
val lift2 : ('a -&gt; 'b -&gt; 'c) -&gt; B<'a> -&gt; B<'b> -&gt; B<'c>
val lift3 : ('a -&gt; 'b -&gt; 'c -&gt; 'd) -&gt; B<'a> -&gt; B<'b> -&gt; B<'c> -&gt; B<'d>

 

    我们将把所有的函数放到 Behavior 模块中,但是,清单没有重复模块的声明。我们的示例首先显示了如何分别实现提升函数,显示它们的签名。注意,我们在打印的类型签名中,把 Behavior 简称为 B。

    实现的有一个参数的提升函数相当简单,因为它做的事与 Behavior.map 相同。这是可能的,唯一是因为有了偏函数应用,因此,C# 的实现将是不同的。lift2 和 lift3 的实现是类似于我们前面看到的 map 函数,相同的模式可清楚地应用于三个以上参数的函数。

    在这里,我们可以实现行为的通用计算,而无需使用低级的 sample 基元。任何你能想到的计算可以使用 time 基元和一个提升函数实现。这是我们早前的两种行为相加的问题的解决:

 

&gt; let added = Behavior.lift2 (+) wiggle time;;
val added : Behavior<float32>

 

    这个示例使用 lift2 函数,传递 + 运算符和两个基元行为。如果我们读取返回的行为值,它将得到两个用作参数的行为的值,并将它们加到一起。我们可以用像之前的相同方式可视化这种行为。图 15.3 显示了这种行为和"平方"行为的版本,稍做修改以避免它太快地关闭屏幕抓图的顶部。

image

图 15.3 图形显示两种复杂行为最初 10 秒的值

 

    你可能会想知道,是否这就是我们在语法方面能做的最好的,毕竟,它仍然有点笨拙。事实上,稍后我们会看到,只需要写成 wiggle + time,但是,首先让我们在 C# 中实现 Behavior.map 和提升函数。

你可能感兴趣的:(职场,abs,休闲,绝对值)