Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

JavaScript专题之偏函数 #43

Open
mqyqingfeng opened this issue Aug 16, 2017 · 27 comments
Open

JavaScript专题之偏函数 #43

mqyqingfeng opened this issue Aug 16, 2017 · 27 comments

Comments

@mqyqingfeng
Copy link
Owner

mqyqingfeng commented Aug 16, 2017

定义

维基百科中对偏函数 (Partial application) 的定义为:

In computer science, partial application (or partial function application) refers to the process of fixing a number of arguments to a function, producing another function of smaller arity.

翻译成中文:

在计算机科学中,局部应用是指固定一个函数的一些参数,然后产生另一个更小元的函数。

什么是元?元是指函数参数的个数,比如一个带有两个参数的函数被称为二元函数。

举个简单的例子:

function add(a, b) {
    return a + b;
}

// 执行 add 函数,一次传入两个参数即可
add(1, 2) // 3

// 假设有一个 partial 函数可以做到局部应用
var addOne = partial(add, 1);

addOne(2) // 3

个人觉得翻译成“局部应用”或许更贴切些,以下全部使用“局部应用”。

柯里化与局部应用

如果看过上一篇文章《JavaScript专题之柯里化》,实际上你会发现这个例子和柯里化太像了,所以两者到底是有什么区别呢?

其实也很明显:

柯里化是将一个多参数函数转换成多个单参数函数,也就是将一个 n 元函数转换成 n 个一元函数。

局部应用则是固定一个函数的一个或者多个参数,也就是将一个 n 元函数转换成一个 n - x 元函数。

如果说两者有什么关系的话,引用 functional-programming-jargon 中的描述就是:

Curried functions are automatically partially applied.

partial

我们今天的目的是模仿 underscore 写一个 partial 函数,比起 curry 函数,这个显然简单了很多。

也许你在想我们可以直接使用 bind 呐,举个例子:

function add(a, b) {
    return a + b;
}

var addOne = add.bind(null, 1);

addOne(2) // 3

然而使用 bind 我们还是改变了 this 指向,我们要写一个不改变 this 指向的方法。

第一版

根据之前的表述,我们可以尝试着写出第一版:

// 第一版
// 似曾相识的代码
function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};

我们来写个 demo 验证下 this 的指向:

function add(a, b) {
    return a + b + this.value;
}

// var addOne = add.bind(null, 1);
var addOne = partial(add, 1);

var value = 1;
var obj = {
    value: 2,
    addOne: addOne
}
obj.addOne(2); // ???
// 使用 bind 时,结果为 4
// 使用 partial 时,结果为 5

第二版

然而正如 curry 函数可以使用占位符一样,我们希望 partial 函数也可以实现这个功能,我们再来写第二版:

// 第二版
var _ = {};

function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var position = 0, len = args.length;
        for(var i = 0; i < len; i++) {
            args[i] = args[i] === _ ? arguments[position++] : args[i]
        }
        while(position < arguments.length) args.push(arguments[position++]);
        return fn.apply(this, args);
    };
};

我们验证一下:

var subtract = function(a, b) { return b - a; };
subFrom20 = partial(subtract, _, 20);
subFrom20(5);

写在最后

值得注意的是:underscore 和 lodash 都提供了 partial 函数,但只有 lodash 提供了 curry 函数。

专题系列

JavaScript专题系列目录地址:https://github.com/mqyqingfeng/Blog

JavaScript专题系列预计写二十篇左右,主要研究日常开发中一些功能点的实现,比如防抖、节流、去重、类型判断、拷贝、最值、扁平、柯里、递归、乱序、排序等,特点是研(chao)究(xi) underscore 和 jQuery 的实现方式。

如果有错误或者不严谨的地方,请务必给予指正,十分感谢。如果喜欢或者有所启发,欢迎 star,对作者也是一种鼓励。

@syzq
Copy link

syzq commented Aug 17, 2017

大佬有公众号吗

@mqyqingfeng
Copy link
Owner Author

@syzq 没有哎~

@SilenceZeng
Copy link

第二版 while(position < arguments.length) args.push(argumetns[position++]); 中后一个arguments拼成argumetns了

@mqyqingfeng
Copy link
Owner Author

@SilenceZeng 非常感谢指出~ 已修改~ o( ̄▽ ̄)d

@luobotang
Copy link

总结得很棒!

@evanzlj
Copy link

evanzlj commented Jan 9, 2018

大佬开个公众号吧,这样可以有更大的动力~~ 赏你一杯咖啡,哈哈~~

@mqyqingfeng
Copy link
Owner Author

@evanzlj 我现在维护三个平台就已经有点忙不过来啦,公众号就更麻烦了,而且我觉得还是 PC 端更适合学习一些,可以边看文章边动手试验代码。

@waterVenice7
Copy link

可不可以这样理解,偏函数的一种特殊情况就是curry

@WangNianyi2001
Copy link

那这样说来Function.prototype.bind也是偏函数咯

@wuzhong1030
Copy link

var _ = {};
args[i] === _
这样写是有什么奥妙么

@kashtian
Copy link

大佬,这个偏函数是不是有点问题,subFrom20(5),subFrom20(4)都输出15,那个args被污染拉,是不是该拷贝一份

@zhw2590582
Copy link

es6简化版

function partial(func, ...argsBound) {
  return function(...args) {
    return func.call(this, ...argsBound, ...args);
  };
}

@lisen6
Copy link

lisen6 commented Mar 27, 2019

大佬,这个偏函数是不是有点问题,subFrom20(5),subFrom20(4)都输出15,那个args被污染拉,是不是该拷贝一份

你写错了吧。没有被污染啊

@Gloomysunday28
Copy link

占位符用在里面有什么用

@cyhwinner
Copy link

cyhwinner commented Jul 26, 2019

 while(position < arguments.length) args.push(arguments[position++]);

直接换成 下面这样会不会好点

if(position < arguments.length) args = args.concat([].slice.call(arguments, position));

@HunterCoder
Copy link

while(position < arguments.length) args.push(arguments[position++])
应该改为:
while(position < arguments.length) args[position]=(arguments[position++]);
@kashtian

@wuming-lzd
Copy link

wuming-lzd commented Oct 21, 2019

@cyhwinner

 while(position < arguments.length) args.push(arguments[position++]);

直接换成 下面这样会不会好点

if(position < arguments.length) args = args.concat([].slice.call(arguments, position));

不一样吧,原文的意思是:将arguments[position]这个值放入到args中;你改后的意思是:将arguments从position位置截取到数组最后一项的所有值放入到args中;原文放入了一个值,你放入了从position位置到最后一个位置的好几个值

@xsfxtsxxr
Copy link

强烈建议大佬把博客文章整理一下搞个类似阮一峰老师的ES6微信小程序

@ZiPengLiang
Copy link

您好,我最近一直在学习您编写的文章,在这一章中我实在分辨不出柯里化和偏函数的区别,还有能说明下这两者的应用场景吗?

@KeithChou
Copy link

// 第一版
// 似曾相识的代码
function partial(fn) {
    var args = [].slice.call(arguments, 1);
    return function() {
        var newArgs = args.concat([].slice.call(arguments));
        return fn.apply(this, newArgs);
    };
};

这段就是bind的实现呀 所以可以理解成 Function.prototype.bind函数也是一个偏函数啦

@anjina
Copy link

anjina commented Dec 13, 2020

@cyhwinner

 while(position < arguments.length) args.push(arguments[position++]);

直接换成 下面这样会不会好点

if(position < arguments.length) args = args.concat([].slice.call(arguments, position));

不一样吧,原文的意思是:将arguments[position]这个值放入到args中;你改后的意思是:将arguments从position位置截取到数组最后一项的所有值放入到args中;原文放入了一个值,你放入了从position位置到最后一个位置的好几个值

你可能没有注意到 第二个替换 while 改成了 if

@fwqaaq
Copy link

fwqaaq commented Feb 25, 2022

使用ts写偏函数,不过类型出于考虑,只用了any

function partical(fn: Function, ...args: any[]): Function {
  return function (...moreArgs: any[]) {
    return fn(...args, ...moreArgs)
  }
}

@yangjunjun
Copy link

更加函数式的实现:

const _ = Symbol('_')
const partial = (fn, ...outerArgs) => {
    return function (...innerArgs) {
        return fn.call(this, ...outerArgs.map(arg => arg === _ ? innerArgs.shift() : arg), ...innerArgs)
    }
}
partial._ = _

@xiaochengzi6
Copy link

xiaochengzi6 commented Aug 30, 2022

函数式的实现:

const _ = Symbol('_')
const partial = (fn, ...outerArgs) => {
    return function (...innerArgs) {
        return fn.call(this, ...outerArgs.map(arg => arg === _ ? innerArgs.shift() : arg), ...innerArgs)
    }
}
partial._ = _

我认为应该这样比较好,占位符可以由编写成员自定义

function partial(fn, holder = partial, ...args) {
  return function (...params) {
    return fn.call(this, ...args.map((arg) => (arg === holder ? (arg = params.shift()) : arg)), ...params)
  }
}~~~
这样看起来就完美了

@cangSDARM
Copy link

“局部应用”翻译也有问题。逻辑上讲应该是 applied partial function, 只是英语语法调整成了partially applied function。正确翻译应该是 “已被传递(applied)部分(partial)(参数)(被省略)的函数”,简称不如为“部分定参函数”

@Zhen-code
Copy link

while(position < arguments.length) args.push(arguments[position++]);
大佬,这个有点看不懂可以解释一下嘛

@yi-xiaobai
Copy link

大佬 bind 和 局部应用 晕了 不太明白呢

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests