8. 柯里化和部分应用

柯里化和部分应用是另外两个直接来自旧数学论文的函数式概念。前者与印度食物毫无关系,尽管它确实很美味,但它以杰出的美国数学家 Haskell Brooks Curry 的名字命名,有不少于三种编程语言以他的名字命名。

柯里化源自 Haskell Curry 在组合逻辑方面的工作,它是现代函数式编程的基础之一。我不会给出枯燥的正式定义,而是通过示例进行解释。这是一个类似于 C# 的 add 函数伪代码:

public interface ICurriedFunctions
{
   
    decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

    var answer = curry.Add(100, 200);

在这个例子中,我们期望答案是 300(即 100+200),事实确实如此。

但是,如果我只提供一个参数会怎样?像这样:

public interface ICurriedFunctions
{
   
 decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

var answer = curry.Add(100); // What could it be?

在这种情况下,如果这是一个假设的柯里化函数,你认为你会在 answer 中返回什么?

我在函数式编程中设计了一个经验法则 - 如果有一个问题,答案很可能是“函数”。这里就是这种情况。

如果这是一个柯里化函数,那么 answer 变量将是一个函数。它将是原始 Add 函数的修改版本,但第一个参数现在固定为值 100 - 有效地使其成为一个新函数,它将 100 添加到您提供的任何内容中。

您可以像这样使用它:

public interface ICurriedFunctions
{
   
 decimal Add(decimal a, decimal b);
}

var curry = // some logic for obtaining an implementation of the interface

var add100 = curry.Add(100); // Func<decimal,decimal>, adds 100 to the input

var answerA = add100(200); // 300 -> 200+100
var answerB = add100(0); // 100 -> 0+100
var answerC = add100(900); // 1000 -> 900+100

它基本上是一种从具有多个参数的函数开始的方法,并从中创建该函数的多个更具体的版本。一个单一的基本函数可以变成许多不同的函数,这与 OO 的继承概念不同。

但是,柯里化的意义究竟是什么?如何使用它?

让我解释一下……

柯里化和大型函数

在我上面给出的“添加”示例中,我们只有一对参数,因此当柯里化可用时,我们只能用它们做两种可能的事情:

  1. 提供第一个参数,返回一个函数

  2. 提供两个参数并返回一个值

柯里化如何处理具有超过 2 个基本参数的函数?为此,我将使用一个简单的 CSV 解析器的示例 - 即获取 CSV 文本文件,将其按行分成记录,然后使用一些分隔符(通常是逗号)将其再次分成记录中的单个属性。

假设我编写了一个解析器函数来加载一批书籍数据:

// Input in the format:
//
//title,author,publicationDate
//The Hitch-Hiker's Guide to the Galaxy,Douglas Adams,1979
//Dimension of Miracles,Robert Sheckley,1968
//The Stainless Steel Rat,Harry Harrison,1957
//The Unorthodox Engineers,Colin Kapp,1979

public IEnumerable<Book> ParseBooks(string fileName) =>
 File.ReadAllText(fileName)
  .Split("\r\n")
  .Skip(1) // Skip the header
  .Select(x => x.split(",").ToArray())
  .Select(x => new Book
  {
   
   Title = x[0],
   Author = x[1],
   PublicationDate = x[2]
  });

var bookData = parseBooks(true, Environment.NewLine, ",", "books.csv");

这一切都很好,只是接下来的两组书的格式不同。Books2.csv 使用竖线而不是逗号来分隔字段,Books3.csv 来自 Linux 环境,行尾为“\n”,而不是 Windows 样式的“\r\n”。

我们可以通过创建 3 个彼此几乎相同的不同函数来解决这个问题。但我并不热衷于不必要的重复,因为它会给想要维护代码库的未来开发人员带来太多问题。

更合理的解决方案是为可能发生变化的所有内容添加参数。像这样:

public IEnumerable<Book> ParseBooks(
 string lineBreak,
 bool skipHeader,
 string fieldDelimiter,
 string fileName
) =>
 File.ReadAllText(fileName)
  .Split(lineBreak)
  .Skip(skipHeader ? 1 : 0)
  .Select(x => x.split(fieldDelimiter).ToArray())
  .Select(x => new Book
  {
   
   Title = x[
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

0neKing2017

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值