#函数式编程 Functional Programming in C# [32]

7.2 克服方法解析的怪癖

  到目前为止,我们已经自由地使用方法、lambdas和委托来表示函数。然而,对于编译器来说,这些都是不同的东西,而且方法的类型推理并不像我们希望的那样好。
  让我们首先看看当一切顺利时会发生什么,比如当我们使用 Option.Map 时:

Some(9.0).Map(Math.Sqrt) // => 3.0

  这里,Math.Sqrt这个名字标识了一个方法,而Map期望一个Func<T,R>类型的委托。 更确切地说,Math.Sqrt标识了一个 “方法组”;由于方法重载,可能会有几个同名的方法。 编译器很聪明,不仅选择了正确的重载(在这种情况下,只有一个),而且还推断出了Func的通用类型,因此我们不需要向Map指定类型参数:

Some(9.0).Map<double, double>(Math.Sqrt)

  这一切都非常好。它使我们不必在方法(或者lambdas)和委托之间进行转换,也不必指定通用类型,因为这些类型可以从方法签名中推断出来。 不幸的是,对于有两个或更多参数的方法,所有这些好处都消失了。
  让我们看看如果我们尝试将 greet 函数重写为一个方法会发生什么——这里称为 GreeterMethod。我们想写的就是这个。

清单 7.2 多参数方法的类型推断失败

PersonalizedGreeting GreeterMethod(Greeting gr, Name name) // 如果我们将问候函数编写为一种方法......
	=> $"{gr}, {name}";
Func<Name, PersonalizedGreeting> GreetWith(Greeting greeting) 
	=> GreeterMethod.Apply(greeting); //... 那么这个表达式不能编译。

  在这里,我们已经把greeter函数写成了一个方法,现在我们想要一个GreetWith方法来把它部分地应用到一个给定的问候语中。不幸的是,这段代码不能编译,因为GreeterMethod这个名字标识了一个MethodGroup,而Apply期望的是一个Func,编译器并没有为我们做出推断。

局部函数中的类型推断 C# 7 引入了“本地函数”——在方法范围内声明的函数——但它们实际上应该被称为“本地方法”。在内部,它们被实现为方法(尽管这没有任何好处——你不能重载它们),所以在类型推断方面,它们具有与普通方法相同的特性。

  如果你想使用通用的Apply来为一个方法提供参数,你必须使用以下形式之一。

清单7.3 使用多参数方法作为HOF的参数需要混乱的语法

PersonalizedGreeting GreeterMethod(Greeting gr, Name name) 
	=> $ "{gr}, {name}";
Func < Name, PersonalizedGreeting > GreetWith_1(Greeting greeting) 
	=> FuncExt.Apply < Greeting, Name, PersonalizedGreeting > //放弃了扩展方法的语法,明确提供了所有的通用参数。
		(GreeterMethod, greeting);
Func < Name, PersonalizedGreeting > GreetWith_2(Greeting greeting) 
	=> new Func < Greeting, Name, PersonalizedGreeting >(GreeterMethod)//在调用Apply之前,明确地将方法转换为delegate。
		.Apply(greeting);

  我个人认为这两种情况下的句法噪音是不可接受的。幸运的是,这些问题是针对方法解析的。如果你使用委托(想想Func),它们就会消失。
  有多种方法可以创建委托。

清单 7.4 获取委托实例的不同方式

public class TypeInference_Delegate {
    string separator = "! ";
    // 1. field   //委托字段的声明和初始化;请注意,您不能在此处引用分隔符。
    Func < Greeting, Name, PersonalizedGreeting > GreeterField 
    	= (gr, name) => $ "{gr}, {name}";
    // 2. property   //一个getter-only属性的主体是由=>引入的。
    Func < Greeting, Name, PersonalizedGreeting > GreeterProperty 
    	=> (gr, name) => $ "{gr}{separator}{name}"; 
    // 3. factory   //一个作为函数工厂的方法可以有通用参数。
    Func < Greeting, T, PersonalizedGreeting > GreeterFactory < T > () 
    	=> (gr, t) => $ "{gr}{separator}{t}";
}

  让我们简单地讨论一下这些选项。声明一个委托字段似乎是最自然的选择。不幸的是,它并不十分强大。例如,如果你把声明和初始化结合起来,如清单7.4所示,你不能在委托体中引用任何实例变量,如分离器。
  这个问题可以通过使用属性来解决。在暴露委托的类中,这相当于只是用 => 替换 = 来声明一个 getter-only 属性,这对客户端代码是完全透明的。但最强大的方法是拥有一个工厂方法:一个用来创建你想要的委托的方法。这里最大的区别是你也可以有泛型参数,这对于字段或属性是不可能的。
  无论您以哪种方式获取委托实例,类型解析都可以正常工作,因此在所有情况下您都可以像这样提供第一个参数:

GreeterField.Apply("Hi");
GreeterProperty.Apply("Hi");
GreeterFactory<Name>().Apply("Hi");

  本节的启示是,如果你想使用以多参数函数为参数的HOF,有时最好不要使用方法,而是写Funcs,或者写返回Funcs的方法。虽然没有方法那么直白,但Funcs为你省去了明确指定类型参数的语法开销,使代码更易读。
  现在你知道了部分应用,让我们继续讨论一个相关的概念:currying。这是一种假设并可以说是简化了部分应用的技术。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值