Нам нужно создать функцию, которая будет печатать результаты для:
console.info(sum(1)(2)(3)(4)(5)(6)());
Итак, вот как я сделал:
let res = 0;
function sum(a){
if (a){
res = res + a;
return sum; //Return the same function for next chain of actions
}else{
return res;
}
}
console.info(sum(1)(2)(3)());
Есть ли способ добиться этого с помощью местного res
? проблема, с которой я сталкиваюсь, делая его локальным, заключается в том, что он просто сбрасывается при следующем вызове.
Я вижу такие решения:
function sum(a) {
return function(b){
if (!b){
return a;
}
return sum(a+b);
}
}
Но то, как я это сделал, кажется мне более естественным и интуитивно понятным, и я просто хочу, чтобы это работало с res
как локальным, а не глобальным.
if (!b)
— ошибочное условное выражение, оно не подходит для таких выражений, как sum(x)(0)(y)()
, потому что 0
— ложное значение.
Вы можете сделать это следующим образом:
Функция innerSum
поддерживает замыкание по res. Это позволяет res сохранять свое значение при нескольких вызовах innerSum
.
function sum(a) {
let res = a;
function innerSum(b) {
if (b !== undefined) {
res += b;
return innerSum;
} else {
return res;
}
}
innerSum.toString = function() {
return res;
};
return innerSum;
}
Демо
function sum(a) {
let res = a;
function innerSum(b) {
if (b !== undefined) {
res += b;
return innerSum;
} else {
return res;
}
}
innerSum.toString = function() {
return res;
};
return innerSum;
}
console.info(sum(1)(2)(3)(4)(5)(6)().toString());
Имеет смысл. Спасибо.
@ABGR Нет проблем, рад помочь
a = sum(1)(2); b = a(3); console.info(a(), b());
возвращает 6, 6
, что кажется неправильным. Рассмотрим несколько реальное использование: price = sum(item1Price)(item2Price); priceWithTax = price(tax);
Рассмотрите возможность использования valueOf вместо toString
, поскольку res
— это number
Вы можете сделать sum
IIFE и поместить res
в локальную область видимости. После этого при вызове без аргументов сбросьте res
на 0
, чтобы подготовить его к следующему вызову.
const sum = (() => {
let res = 0;
return function (a) {
if (typeof a === 'number') {
res = res + a;
return sum;
} else {
const temp = res;
res = 0;
return temp;
}
}
})();
console.info(sum(1)(2)(3)());
console.info(sum(1)(2)(3)(4)());
Его также можно сократить, используя более простой, но, возможно, проклятый синтаксис:
const sum = (res => a => {
if (typeof a === 'number') {
res = res + a;
return sum;
}
try {
return res;
} finally {
res = 0;
}
})(0);
console.info(sum(1)(2)(3)());
console.info(sum(1)(2)(3)(4)());
В этом есть смысл. Спасибо.
Несколько противоположная проблема вот: a = sum(1)(2); b = a(3); console.info(a(), b());
возвращается 6, 0
@VLAZ Я не уверен, есть ли хороший способ решить эту проблему, кроме того, каков будет ожидаемый результат? 3, 6
?
Да, 1+2 = 3 и 1+2+3=6, поэтому 3, 6 верно. И это достижимо путем переноса состояния внутри связанных вызовов, а не разделения его на все цепочки. Один из самых простых способов реализовать это можно увидеть здесь : const sum= x => y => (y !== undefined) ? sum(x + y) : x;
- если указан аргумент, перенести сумму вперед. Смотрите демо
@VLAZ Теперь я понимаю, это действительно намного проще. Одна маленькая проблема, о которой sum
нужно упомянуть как минимум дважды. Я бы сказал, что sum()
должно вернуться 0
, хотя?
Ну, вы тоже можете это сделать. Хотя, учитывая, что сложение — бинарная операция, вполне разумно ожидать как минимум двух аргументов. Существует множество реализаций бесконечного каррирования, я выбрал ту, которую легко опубликовать в комментарии. Если вы действительно хотите, вы можете сделать что-то вроде этого: jsbin.com/pubuduxati/1/edit?js,console
@VLAZ пропустил эти тестовые случаи. Спасибо. Это более всеобъемлющий подход.
(Обновлено, чтобы включить в комментарии предложение Мулан.)
Интересная альтернатива — воспользоваться valueOf
, toString
и toJSON
, чтобы нам не приходилось выполнять последний пустой вызов:
const sum = (a) => Object.assign(
(b) => sum(a + b),
{valueOf: () => a, toString: () => `${a}`, toJSON: () => a }
)
Например, sum(1)(2)(3)
— это функция, но поскольку у нее есть метод valueOf
, мы можем использовать ее для дальнейших вычислений, как если бы это было число:
sum(1)(2)(3) * 10 //=> 60
И если мы используем его при построении строк, будет использоваться версия его значения toString
:
console.info(sum(1)(2)(3)) //=> 6
`The total is ${sum(1)(2)(3)}` //=> "The total is 6"
Обратите внимание, что sum(1)(2)(3)
— это неизменяемая оболочка значения 6
. Добавление новых вызовов просто вернет новые функции, которые не мешают друг другу:
const firstThree = sum(1)(2)(3)
console.info(firstThree(4)(5)(6)) //=> "21"
console.info(firstThree(10)) //=> "16" (previous 4, 5, 6 ignored)
Это полезно, но не панацея. Есть места, где функциональная природа нашего значения может вызвать проблемы. Первое, что приходит на ум, — использовать ли это как значение JSON:
console.info(JSON.stringify({a: 4, b: sum(1)(2)(3), c: 10}))//=> {a, 4, c: 10} // *** Note that `b` is missing ***
Поэтому вам нужно будет определить, есть ли у вас подобные варианты использования. Если нет, то это довольно элегантная версия, как в использовании, так и в реализации.
Обновите предложение Мулан об использовании toJSON
, а также valueOf
и toString
, что означает, что мы можем извлечь представление JSON, которое включает текущее значение этой функции. Конечно, он больше не имеет динамического поведения, но этого и следовало ожидать, когда он был сериализован в строку.
console.info(JSON.stringify({a: 4, b: sum(1)(2)(3), c: 10}))
//=> '{"a", 4, "b": 6, "c": 10}' // *** Note that `b` is now fixed ***
Вы можете протестировать все это в своем браузере в следующем фрагменте:
const sum = (a) => Object.assign(
(b) => sum(a + b),
{valueOf: () => a, toString: () => `${a}`, toJSON: () => a }
)
const res = sum(1)(2)(3) * 10
console.info(res) //=> 60
console.info(sum(1)(2)(3)) //=> 6
console.info(`The total is ${sum(1)(2)(3)}`) //=> "The total is 6"
const firstThree = sum(1)(2)(3)
console.info(firstThree(4)(5)(6)) //=> "21"
console.info(firstThree(10)) //=> "16" (previous 4, 5, 6 ignored)
console.info(JSON.stringify({a: 4, b: sum(1)(2)(3), c: 10}))
//=> '{"a", 4, "b": 6, "c": 10}' // *** Note that `b` is now fixed ***
.as-console-wrapper {max-height: 100% !important; top: 0}
для поддержки JSON.stringify
вы можете добавить { ..., toJSON: () => a }
к назначениям объекта (функции) :D
@Мулан: совершенно верно. Обновлено. По какой-то причине я никогда не использую toJSON
, хотя точно знаю, что он существует. Этот чехол идеально подходит для этого!
все еще размышляю valueOf
.. Я понимаю, что не знаю всех условий, которые вызывают вызов метода. определенно пробел в знаниях, который мне нужно заполнить
Я видел части спецификации, но никогда не исследовал их подробно. Но я думаю, что общее правило таково: если объект предоставляется там, где ожидается числовое значение, мы вызываем valueOf
, если он существует.
Вы можете определить sum
так:
const sum = a => b =>
b == null ? a : sum(a + b)
console.info(sum(1)(2)(3)(4)()) // 10
console.info(sum(11)(11)(11)()) // 33
Если вы хотите поддержать sum()
и вернуть 0
, необходимо еще одно условие -
const sum = a =>
a == null
? 0
: b =>
b == null ? a : sum(a + b)
console.info(sum(1)(2)(3)(4)()) // 10
console.info(sum(11)(11)(11)()) // 33
console.info(sum()) // 0
sum
немного негибкий и поддерживает только сложение. Лучший шаблон, разрешающий любую функцию, можно увидеть, продемонстрировав go
ниже:
function go(a) {
return function map(fn) {
return go(fn(a))
}
}
const add = (a) => (b) => a + b
const mul = (a) => (b) => a * b
go(2) // 2
(add(3)) // 5
(add(4)) // 9
(mul(11)) // 99
(console.info) // => 99
Далее мы увидим go
как хорошо типизированную функцию —
interface Go<A> {
<B>(fn: (a: A) => B): Go<B>
}
function go<A>(a: A) {
return function map<B>(fn: (a: A) => B): Go<B> {
return go(fn(a))
}
}
const add = (a: number) => (b: number) =>
a + b
const mul = (a: number) => (b: number) =>
a * b
Попробуйте на игровой площадке для машинописи
Если безопасность типов вас не беспокоит, вы можете написать go
просто:
const go = x => fn => go(fn(x))
Очень хорошо. Это очень напоминает мне демо-версию, выпущенную более десяти лет назад, которая вдохновила нас с другом на наше путешествие по ФП. Конечно, если вы определите tap
(= (fn) => (x) => (fn(x), x)
), вы можете обернуть его вокруг своих утверждений console.info
и продолжить цепочку.
чтение этой темы вернуло меня назад 🥲
Есть ли причина, по которой вообще должна быть переменная? Кажется намеренно ограничивающим, но без какой-либо реальной причины или даже выгоды. Традиционные и наиболее правильные подходы используют частичное применение или перенос состояния через аргументы. Оба ответа имеют проблемы с состоянием из-за того, что этого не сделали.