Есть ли способ добиться каррирования с локальной переменной?

Нам нужно создать функцию, которая будет печатать результаты для: 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 как локальным, а не глобальным.

Есть ли причина, по которой вообще должна быть переменная? Кажется намеренно ограничивающим, но без какой-либо реальной причины или даже выгоды. Традиционные и наиболее правильные подходы используют частичное применение или перенос состояния через аргументы. Оба ответа имеют проблемы с состоянием из-за того, что этого не сделали.

VLAZ 27.06.2024 11:13
if (!b) — ошибочное условное выражение, оно не подходит для таких выражений, как sum(x)(0)(y)(), потому что 0 — ложное значение.
Mulan 02.07.2024 15:17
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
Поведение ключевого слова "this" в стрелочной функции в сравнении с нормальной функцией
В JavaScript одним из самых запутанных понятий является поведение ключевого слова "this" в стрелочной и обычной функциях.
Концепция локализации и ее применение в приложениях React ⚡️
Концепция локализации и ее применение в приложениях React ⚡️
Локализация - это процесс адаптации приложения к различным языкам и культурным требованиям. Это позволяет пользователям получить опыт, соответствующий...
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
Улучшение производительности загрузки с помощью Google Tag Manager и атрибута Defer
В настоящее время производительность загрузки веб-сайта имеет решающее значение не только для удобства пользователей, но и для ранжирования в...
Безумие обратных вызовов в javascript [JS]
Безумие обратных вызовов в javascript [JS]
Здравствуйте! Юный падаван 🚀. Присоединяйся ко мне, чтобы разобраться в одной из самых запутанных концепций, когда вы начинаете изучать мир...
Система управления парковками с использованием HTML, CSS и JavaScript
Система управления парковками с использованием HTML, CSS и JavaScript
Веб-сайт по управлению парковками был создан с использованием HTML, CSS и JavaScript. Это простой сайт, ничего вычурного. Основная цель -...
JavaScript Вопросы с множественным выбором и ответы
JavaScript Вопросы с множественным выбором и ответы
Если вы ищете платформу, которая предоставляет вам бесплатный тест JavaScript MCQ (Multiple Choice Questions With Answers) для оценки ваших знаний,...
1
2
103
4
Перейти к ответу Данный вопрос помечен как решенный

Ответы 4

Вы можете сделать это следующим образом:

Функция 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 27.06.2024 09:46

@ABGR Нет проблем, рад помочь

Carsten Løvbo Andersen 27.06.2024 09:54
a = sum(1)(2); b = a(3); console.info(a(), b()); возвращает 6, 6, что кажется неправильным. Рассмотрим несколько реальное использование: price = sum(item1Price)(item2Price); priceWithTax = price(tax);
VLAZ 27.06.2024 10:45

Рассмотрите возможность использования valueOf вместо toString, поскольку res — это number

Hao Wu 27.06.2024 11:19
Ответ принят как подходящий

Вы можете сделать 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)());

В этом есть смысл. Спасибо.

ABGR 27.06.2024 09:46

Несколько противоположная проблема вот: a = sum(1)(2); b = a(3); console.info(a(), b()); возвращается 6, 0

VLAZ 27.06.2024 10:48

@VLAZ Я не уверен, есть ли хороший способ решить эту проблему, кроме того, каков будет ожидаемый результат? 3, 6?

Hao Wu 27.06.2024 11:08

Да, 1+2 = 3 и 1+2+3=6, поэтому 3, 6 верно. И это достижимо путем переноса состояния внутри связанных вызовов, а не разделения его на все цепочки. Один из самых простых способов реализовать это можно увидеть здесь : const sum= x => y => (y !== undefined) ? sum(x + y) : x; - если указан аргумент, перенести сумму вперед. Смотрите демо

VLAZ 27.06.2024 11:25

@VLAZ Теперь я понимаю, это действительно намного проще. Одна маленькая проблема, о которой sum нужно упомянуть как минимум дважды. Я бы сказал, что sum() должно вернуться 0, хотя?

Hao Wu 27.06.2024 11:35

Ну, вы тоже можете это сделать. Хотя, учитывая, что сложение — бинарная операция, вполне разумно ожидать как минимум двух аргументов. Существует множество реализаций бесконечного каррирования, я выбрал ту, которую легко опубликовать в комментарии. Если вы действительно хотите, вы можете сделать что-то вроде этого: jsbin.com/pubuduxati/1/edit?js,console

VLAZ 27.06.2024 11:58

@VLAZ пропустил эти тестовые случаи. Спасибо. Это более всеобъемлющий подход.

ABGR 27.06.2024 12:33

(Обновлено, чтобы включить в комментарии предложение Мулан.)

Интересная альтернатива — воспользоваться 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

Mulan 01.07.2024 17:59

@Мулан: совершенно верно. Обновлено. По какой-то причине я никогда не использую toJSON, хотя точно знаю, что он существует. Этот чехол идеально подходит для этого!

Scott Sauyet 02.07.2024 15:24

все еще размышляю valueOf.. Я понимаю, что не знаю всех условий, которые вызывают вызов метода. определенно пробел в знаниях, который мне нужно заполнить

Mulan 02.07.2024 15:36

Я видел части спецификации, но никогда не исследовал их подробно. Но я думаю, что общее правило таково: если объект предоставляется там, где ожидается числовое значение, мы вызываем valueOf, если он существует.

Scott Sauyet 02.07.2024 15:38

Вы можете определить 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 и продолжить цепочку.

Scott Sauyet 02.07.2024 15:36

чтение этой темы вернуло меня назад 🥲

Mulan 02.07.2024 20:50

Другие вопросы по теме