跳至主要內容

偏函数和柯里化

zfh大约 4 分钟约 1306 字

柯里化

在计算机科学中,柯里化(英语:Currying),又译为卡瑞化或加里化,是把接受多个参数的函数变换成接受一个单一参数(最初函数的第一个参数)的函数,并且返回接受余下的参数而且返回结果的新函数的技术。

讲人话就是,柯里化是把接受 n 个参数的 1 个函数改造为只接受 1个参数的 n 个互相嵌套的函数的过程。也就是 f(a,b,c) 会变成 f(a)(b)(c)

举个例子

为啥要改造?如何改造?我们直接把自己代入场景里来改造一个看看,改造完就啥都明白了:

我们现在是一家电商公司,旗下有多个电商站点。为了确保商品名的唯一性,我们考虑使用 prefix(一个标识不同站点的前缀字符串)、 type(商品类型)、name(商品原本名称)三个字符串拼接的方式来为商品生成一个完整版名称。对应的方法如下:

function generateName(prefix, type, itemName) {
    return prefix + type + itemName
}

我们看到这个方法里需要视情况传入 prefix、type、name 参数。如果是作为一个细分工种的 leader,我可能只会负责一个站点的业务。比如我负责了 “大卖网” 的业务,那我每次生成商品名时,都会这样传参:

// itemName 是原有商品名
generateName('大卖网', type, itemName)

发现问题没有?这里面 prefix 其实是一个固定的入参,而我们每次都还要手动把它告诉给 generateName 函数,这很不爽。

如果是作为一个细分工种的程序员,我负责的东西可能更具体了,比如仅仅负责 “大卖网” 站点下的 “母婴” 类商品,那么我每次生成完整名称的时候,调用这个函数就是这样传参的:

// itemName 是原有商品名
generateName('大卖网', '母婴', itemName)

隔壁组的小哥,他只负责 “洗菜网” 站点下的 “生鲜” 类商品,那么他每次是这样传参的:

// itemName 是原有商品名
generateName('洗菜网', '生鲜', itemName)

一样的道理,无论是站在我的角度、还是隔壁组小哥的角度,对我们各自来说,调用 generateName 时其实真正的变量只有 itemName 一个,而我们却每次都不得不把前两个参数也手动传一遍。

此时我们多么希望,有一种魔法,可以让函数在必要的情况下帮我们 “记住” 一部分入参。在这个场景下,柯里化可以帮我们很大的忙。现在我们对 generateName 进行柯里化(解析在注释里):

function generateName(prefix) {  
    return function(type) {
        return function (itemName) {
            return prefix + type + itemName
        }    
    }
}

// 生成大卖网商品名专属函数
var salesName = generateName('大卖网')

// “记住”prefix,生成大卖网母婴商品名专属函数
var salesBabyName = salesName('母婴')

// "记住“prefix和type,生成洗菜网生鲜商品名专属函数
var vegFreshName = generateName('洗菜网')('生鲜')

// 输出 '大卖网母婴奶瓶'
salesBabyName('奶瓶')
// 输出 '洗菜网生鲜菠菜'
vegFreshName('菠菜')

// 啥也不记,直接生成一个商品名
var itemFullName = generateName('洗菜网')('生鲜')('菠菜')

我们看到,在新的 generateName 函数中,我们可以以自由变量的形式将 prefix、type 的值保留在 generateName 内部的两层嵌套的外部作用域里。

这样一来,原有的 generateName (prefix, type, name) 现在经过柯里化已经变成了 generateName (prefix)(type)(itemName)。通过后者这种形式,我们可以选择性地决定是否要 “记住” prefix、type,从而即时地生成更加符合我们预期的、复用程度更高的目标函数。此外,柯里化还可以帮助我们以嵌套的形式把多个函数的能力组合到一起,这就是柯里化的魅力。

偏函数

偏函数是说,固定你函数的某一个或几个参数,然后返回一个新的函数(这个函数用于接收剩下的参数)。你有 10 个入参,你可以只固定 2 个入参,然后返回一个需要 8 个入参的函数 —— 偏函数应用是不强调 “单参数” 这个概念的。它的目标仅仅是把函数的入参拆解为两部分。

除了约束条件与柯里化略有不同,偏函数在动机和实现思路上都与柯里化一致 —— 动机就是为了 “记住” 函数的一部分参数,实现思路就是走闭包。

仍然是上面的例子。我们单纯地把一口气传入 3 个入参,拆分为先传 1 个、再传 2 个,这样就算实现了偏函数应用:

原有的函数形式与调用方法

function generateName(prefix, type, itemName) {
    return prefix + type + itemName
}

// 调用时一口气传入3个入参
var itemFullName = generateName('大卖网', '母婴', '奶瓶')

偏函数应用改造:

function generateName(prefix) {
    return function(type, itemName) {
        return prefix + type + itemName
    }
}

// 把3个参数分两部分传入
var itemFullName = generateName('大卖网')('母婴', '奶瓶')
上次编辑于:
本站勉强运行 小时