Aad*_*hah 10 javascript haskell lambda-calculus currying partial-application
我curry在JavaScript中编写了一个简单的函数,可以在大多数情况下正常工作
const add = curry((a, b, c) => a + b + c);
const add2 = add(2);
const add5 = add2(3);
console.log(add5(5));Run Code Online (Sandbox Code Playgroud)
<script>
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried] || initArgs.length >= functor.length)
return functor(...initArgs);
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
</script>Run Code Online (Sandbox Code Playgroud)
但是,它不适用于以下情况:
// length :: [a] -> Number
const length = a => a.length;
// filter :: (a -> Bool) -> [a] -> [a]
const filter = curry((f, a) => a.filter(f));
// compose :: (b -> c) -> (a -> b) -> a -> c
const compose = curry((f, g, x) => f(g(x)));
// countWhere :: (a -> Bool) -> [a] -> Number
const countWhere = compose(compose(length), filter);
Run Code Online (Sandbox Code Playgroud)
根据以下问题countWhere定义为(length .) . filter:
因此我应该可以使用countWhere如下:
const odd = n => n % 2 === 1;
countWhere(odd, [1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
但是,它不返回3(数组的长度[1,3,5]),而是返回一个函数.我究竟做错了什么?
Tha*_*you 14
@Aadit,
我发布这个是因为你在我的答案中分享了一个评论,以功能方式在javascript中"组合"功能?我没有特别报道该帖子中的currying,因为这是一个非常有争议的话题,而不是我想在那里打开的一堆蠕虫.
当你似乎在你的实现中添加你自己的糖和便利时,我会谨慎使用"如何正确咖喱"的措辞.
无论如何,除此之外,我真的不打算将其作为议论/好斗的帖子.我希望能够就JavaScript的currying进行公开,友好的讨论,同时强调我们的方法之间的一些差异.
无需再费周折...
澄清:
给定f是一个功能而且f.length是n.我们curry(f)是g.我们g用m争论来称呼.应该怎么办?你说:
- 如果
m === 0那么就回来了g.- 如果
m < n然后部分适用f于m新的论据,并返回其接受余下的一个新的咖喱函数n - m的参数.- 如果
m === n然后适用f于m参数.如果结果是一个函数,那么咖喱结果.最后,返回结果.- 如果
m > n那么适用f于第一个n参数.如果结果是一个函数,那么咖喱结果.最后,将结果应用于其余m - n参数并返回新结果.
让我们看一下@Aadit M Shah的代码实际上做的代码示例
var add = curry(function(x, y) {
return function(a, b) {
return x + y + a + b;
}
});
var z = add(1, 2, 3);
console.log(z(4)); // 10
Run Code Online (Sandbox Code Playgroud)
这里发生了两件事:
我不相信有很多的空间,在这里争论,但人们似乎错过了什么钻营实际上是
via:Wikipedia
在数学和计算机科学中,currying是一种翻译函数求值的技术,该函数将多个参数(或参数元组)转换为评估函数序列,每个函数都有一个参数 ...
我最后一点粗暴,因为它非常重要; 序列中的每个函数只接受一个参数 ; 不像你建议的变量(0,1或更多)参数.
你也在帖子中提到了haskell,所以我假设你知道Haskell没有带有多个参数的函数.(注意:一个带元组的函数仍然只是一个带有一个参数,一个元组的函数).其原因是深刻的,并为您提供了具有可变参数的函数无法提供给您的表现力的灵活性.
那么让我们再问一下这个原始问题:应该怎么办?
好吧,当每个函数只接受1个参数时,这很简单.在任何时候,如果给出超过1个参数,它们就会被丢弃.
function id(x) {
return x;
}
Run Code Online (Sandbox Code Playgroud)
我们打电话会发生什么id(1,2,3,4)?当然,我们只能得到1回报并2,3,4完全被忽视.这是:
curry解决方案curous技术ànanaomik
在这种方法中,我们编写一个curry函数,它不断返回单参数函数,直到指定了所有参数
由于这个实现,我们有6个多功能功能.
// no nonsense curry
const curry = f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (f.length, [])
}
// demo
let sum3 = curry(function(x,y,z) {
return x + y + z;
});
console.log (sum3 (3) (5) (-1)); // 7Run Code Online (Sandbox Code Playgroud)
好的,所以我们已经看到了curry一种使用简单的辅助循环实现的技术.它没有依赖关系和一个低于5行代码的声明性定义.它允许部分应用函数,一次一个参数,就像一个curried函数应该工作一样.
没有魔法,没有不可预见的自动调整,没有其他不可预见的后果.
但无论如何,究竟是什么意思呢?
好吧,事实证明,我并没有真正curry发挥作用.正如您在下面看到的,我通常以curry形式定义所有可重用的函数.所以,实际上,只有curry当你想要与一些你无法控制的函数接口时,你才需要它们,或许来自lib或者某些东西; 其中一些可能有可变接口!
有请 curryN
// the more versatile, curryN
const curryN = n => f => {
const aux = (n, xs) =>
n === 0 ? f (...xs) : x => aux (n - 1, [...xs, x])
return aux (n, [])
};
// curry derived from curryN
const curry = f => curryN (f.length) (f);
// some caveman function
let sumN = function() {
return [].slice.call(arguments).reduce(function(a, b) {
return a + b;
});
};
// curry a fixed number of arguments
let g = curryN (5) (sumN);
console.log (g (1) (2) (3) (4) (5)); // 15Run Code Online (Sandbox Code Playgroud)
咖喱或不咖喱?就是那个问题
我们将编写一些示例,其中我们的函数都是curry形式.功能将非常简单.每个都带有1参数,每个都有一个返回表达式.
// composing two functions
const comp = f => g => x => f (g (x))
const mod = y => x => x % y
const eq = y => x => x === y
const odd = comp (eq (1)) (mod (2))
console.log (odd(1)) // true
console.log (odd(2)) // falseRun Code Online (Sandbox Code Playgroud)
你的countWhere功能
// comp :: (b -> c) -> (a -> b) -> (a -> c)
const comp = f => g => x =>
f(g(x))
// mod :: Int -> Int -> Int
const mod = x => y =>
y % x
// type Comparable = Number | String
// eq :: Comparable -> Comparable -> Boolean
const eq = x => y =>
y === x
// odd :: Int -> Boolean
const odd =
comp (eq(1)) (mod(2))
// reduce :: (b -> a -> b) -> b -> ([a]) -> b
const reduce = f => y => ([x,...xs]) =>
x === undefined ? y : reduce (f) (f(y)(x)) (xs)
// filter :: (a -> Boolean) -> [a] -> [a]
const filter = f =>
reduce (acc => x => f (x) ? [...acc,x] : acc) ([])
// length :: [a] -> Int
const length = x =>
x.length
// countWhere :: (a -> Boolean) -> [a] -> Int
const countWhere = f =>
comp (length) (filter(f));
console.log (countWhere (odd) ([1,2,3,4,5]))
// 3Run Code Online (Sandbox Code Playgroud)
备注
咖喱或不咖喱?
// to curry
const add3 = curry((a, b, c) =>
a + b + c
)
// not to curry
const add3 = a => b => c =>
a + b + c
Run Code Online (Sandbox Code Playgroud)
由于ES6箭头功能是当今JavaScripter的首选,我认为手动调整功能的选择是不言而喻的.它实际上更短,并且只需要以咖喱形式写出它就可以减少开销.
也就是说,你仍然会与不提供他们所暴露的功能的curry形式的lib接口.对于这种情况,我建议
curry和curryN(定义如上)partial(如此处所定义)@Iven,
你的curryN实现非常好.本节仅供您使用.
const U = f=> f (f)
const Y = U (h=> f=> f(x=> h (h) (f) (x)))
const curryN = Y (h=> xs=> n=> f=>
n === 0 ? f(...xs) : x=> h ([...xs, x]) (n-1) (f)
) ([])
const curry = f=> curryN (f.length) (f)
const add3 = curry ((x,y,z)=> x + y + z)
console .log (add3 (3) (6) (9))Run Code Online (Sandbox Code Playgroud)
Aad*_*hah 12
你的问题curry的功能(和大多数 功能是人写在JavaScript)的是,它不能正确处理额外的参数.curry
是什么curry呢
假设f是一个功能而且f.length是n.我们curry(f)是g.我们g用m争论来称呼.应该怎么办?
m === 0那么就回来了g.m < n然后部分适用f于m新的论据,并返回其接受余下的一个新的咖喱函数n - m的参数.f于m参数和返回结果.这是大多数curry功能所做的,这是错误的.前两种情况是正确的,但第三种情况是错误的.相反,它应该是:
m === 0那么就回来了g.m < n然后部分适用f于m新的论据,并返回其接受余下的一个新的咖喱函数n - m的参数.m === n然后适用f于m参数.如果结果是函数,那么curry结果.最后,返回结果.m > n那么适用f于第一个n参数.如果结果是函数,那么curry结果.最后,将结果应用于其余m - n参数并返回新结果.大多数curry功能的问题
请考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
如果我们使用不正确的curry函数,那么这相当于:
compose(compose(length), filter, odd, [1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
但是,compose只接受三个参数.最后一个参数被删除:
const compose = curry((f, g, x) =>f(g(x)));
Run Code Online (Sandbox Code Playgroud)
因此,上面的表达式评估为:
compose(length)(filter(odd));
Run Code Online (Sandbox Code Playgroud)
这进一步评估为:
compose(length, filter(odd));
Run Code Online (Sandbox Code Playgroud)
该compose函数需要一个参数,这就是它返回函数而不是返回的原因3.要获得正确的输出,您需要编写:
countWhere(odd)([1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
这就是为什么大多数curry功能都是错误的.
解决方案使用正确的curry功能
再次考虑以下代码:
const countWhere = compose(compose(length), filter);
countWhere(odd, [1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
如果我们使用正确的curry函数,那么这相当于:
compose(compose(length), filter, odd)([1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
评估结果为:
compose(length)(filter(odd))([1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
进一步评估(跳过中间步骤):
compose(length, filter(odd), [1,2,3,4,5]);
Run Code Online (Sandbox Code Playgroud)
结果如下:
length(filter(odd, [1,2,3,4,5]));
Run Code Online (Sandbox Code Playgroud)
产生正确的结果3.
执行正确的curry功能
请注意,我不是curry用来将curry对象转换为数组,因为这是V8中的优化杀手:
const curried = Symbol("curried");
Object.defineProperty(curry, curried, { value: true });
function curry(functor, ...initArgs) {
if (arguments.length === 0) return curry;
if (typeof functor !== "function") {
const value = JSON.stringify(functor);
throw new TypeError(`${value} is not a function`);
}
if (functor[curried]) return functor(...initArgs);
const arity = functor.length;
const args = initArgs.length;
if (args >= arity) {
const result = functor(...initArgs.slice(0, arity));
return typeof result === "function" || args > arity ?
curry(result, ...initArgs.slice(arity)) : result;
}
const result = (...restArgs) => curry(functor, ...initArgs, ...restArgs);
return Object.defineProperty(result, curried, { value: true });
}
Run Code Online (Sandbox Code Playgroud)
我不确定这个实现的速度有多快curry.也许有人可以让它更快.
使用正确curry功能的含义
使用正确的id函数可以直接将Haskell代码转换为JavaScript.例如:
const id = curry(a => a);
const flip = curry((f, x, y) => f(y, x));
Run Code Online (Sandbox Code Playgroud)
该flip函数很有用,因为它允许您轻松地部分应用非curried函数:
const add = (a, b) => a + b;
const add2 = id(add, 2);
Run Code Online (Sandbox Code Playgroud)
该compose函数很有用,因为它允许您在JavaScript中轻松创建正确的部分:
const sub = (a, b) => a - b;
const sub2 = flip(sub, 2); // equivalent to (x - 2)
Run Code Online (Sandbox Code Playgroud)
这也意味着你不需要像这个扩展length功能的黑客:
你可以简单地写:
const project = compose(map, pick);
Run Code Online (Sandbox Code Playgroud)
如问题中所述,如果您想要撰写filter,(f .) . g然后使用该compose模式:
另一种解决方案是创建更高阶的curry函数:
const compose2 = compose(compose, compose);
const countWhere = compose2(length, fitler);
Run Code Online (Sandbox Code Playgroud)
由于chain功能的正确实现,这一切都是可能的.
额外的食物供您考虑
curry当我想要组成一系列函数时,我通常使用以下函数:
const chain = compose((a, x) => {
var length = a.length;
while (length > 0) x = a[--length](x);
return x;
});
Run Code Online (Sandbox Code Playgroud)
这允许您编写如下代码:
const inc = add(1);
const foo = chain([map(inc), filter(odd), take(5)]);
foo([1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
Run Code Online (Sandbox Code Playgroud)
这相当于以下Haskell代码:
let foo = map (+1) . filter odd . take 5
foo [1,2,3,4,5,6,7,8,9,10]
Run Code Online (Sandbox Code Playgroud)
它还允许您编写如下代码:
chain([map(inc), filter(odd), take(5)], [1,2,3,4,5,6,7,8,9,10]); // [2,4,6]
Run Code Online (Sandbox Code Playgroud)
这相当于以下Haskell代码:
map (+1) . filter odd . take 5 $ [1,2,3,4,5,6,7,8,9,10]
Run Code Online (Sandbox Code Playgroud)
希望有所帮助.
| 归档时间: |
|
| 查看次数: |
3461 次 |
| 最近记录: |