#3 Пишем функцию curry в Javascript

poster
В этом же видео мы с вами попробуем написать функцию curry самостоятельно, для лучшего понимания того, как она работает.
Понравилось? Поделитесь с друзьями!
Понравилось?
Поделитесь с друзьями!
Комментарии
Текст видео

В предыдущем видео мы с вами разобрали, зачем нужна функция curry и реальные примеры ее применения.

В этом же видео мы с вами попробуем написать функцию curry самостоятельно, для лучшего понимания того, как она работает. Замечу также, что на собеседованиях часто любят спрашивать: "Знаете ли вы каррирование? А зачем оно нужно? А функцию curry написать на бумажке можете?".

Давайте попробуем.

У меня есть файл html, где подключен наш javascript файл.

Давайте добавим простую функцию, на которой будем тестировать каррирование. Эта функция просто будет складывать 3 числа.

var add = function (a, b, c) {
  return a + b + c
}

И добавим переменную curriedAdd

var curriedAdd = curry(add)

Теперь давайте, чуть выше, создадим curry функцию. Она будет принимать на вход функцию. В нашем случае функцию add.

var curry = function (fn) {
}

Теперь нам необходимо найти общее количество аргументов в функции, которую мы передали на вход. Это можно сделать с помощью .length.

var curry = function (fn) {
  var arity = fn.length
}

Давайте добавим console.log и посмотрим в браузер. У нас вывелось 3, так как у нас 3 аргумента в функции add.

Как мы помним, каррируемая функция всегда возвращает функцию. Давайте добавим ее.

var curry = function (fn) {
  var arity = fn.length

  return function f1(...args) {
    console.log('f1 args', args)
  }
}

var add = function (a, b, c) {
  return a + b + c
}

var curriedAdd = curry(add)

var result = curriedAdd(1)

console.log('result', result)

Напомню, что три точки - это оператор spread. Это оператор из es6, который, в нашем случае, возвращает все аргументы.

Если мы вызовем curriedAdd и передадим в нее 1, то в консоли мы увидим, что наша функция f1 вызвалась и в args у нас содержится массив с нашим аргументом 1.

Также, обратите внимание, что arity, которую мы описали ранее, с момента, когда мы создали переменную curriedAdd, хранится в замыкании. Именно поэтому, мы можем ее сейчас использовать внутри функции f1.

Что нам осталось еще написать? Мы хотим проверить передали ли нам достаточно аргументов, чтобы вызвать функцию или нет.

var curry = function (fn) {
  var arity = fn.length

  return function f1(...args) {
    console.log('f1 args', args)
    if (args.length >= arity) {
      console.log('enough arguments')
    } else {
      console.log('need more arguments')
    }
  }
}

Если мы посмотрим в браузер, то увидим сообщение, что аргументов недостаточно.

Давайте сначала добавим содержимое if, потому что оно проще. Если мы получили достаточно аргументов, мы хотим вызвать исходную функцию fn с args и вернуть результат.

var curry = function (fn) {
  var arity = fn.length

  return function f1(...args) {
    console.log('f1 args', args)
    if (args.length >= arity) {
      console.log('enough arguments')
      return fn(...args)
    } else {
      console.log('need more arguments')
    }
  }
}

Обратите внимание, что я опять применил оператор spread. Так как args был у нас массивом аргументов, то чтобы его конвертировать обратно в аргументы через запятую - нам нужен spread.

То есть мы вызвали функцию fn, передали в нее аргументы и вернули результат.

Если мы сейчас изменим нашу функцию add и у нее будет 1 аргумент, то наше условие отработает, и в result мы получим ответ.

Теперь давайте вернем аргументы add обратно и допишем наш else.

Если у нас недостаточно аргументов, то мы хотим вернуть новую функцию, в которой мы тоже хотим узнать аргументы

var curry = function (fn) {
  var arity = fn.length

  return function f1(...args) {
    console.log('f1 args', args)
    if (args.length >= arity) {
      console.log('enough arguments')
      return fn(...args)
    } else {
      console.log('need more arguments')
      return function f2(...moreArgs) {
        console.log('f2', moreArgs)
      }
    }
  }
}

Теперь давайте вызовем curriedAdd еще раз.

var result = curriedAdd(1)(2)

Если мы посмотрим в браузер, то у нас вывелось f2 с массивом, который содержит нашу двойку.

Обратите внимание, что сейчас нам доступно не только arity из первой функции, но и args из второй функции.

Поэтому мы можем сконкатить args и moreArgs, чтобы вызвать функцию f1 с новыми аргументами.

var curry = function (fn) {
  var arity = fn.length

  return function f1(...args) {
    console.log('f1 args', args)
    if (args.length >= arity) {
      console.log('enough arguments')
      return fn(...args)
    } else {
      console.log('need more arguments')
      return function f2(...moreArgs) {
        console.log('f2', moreArgs)
        var newArgs = args.concat(moreArgs)
        return f1(...newArgs)
      }
    }
  }
}

То есть мы делаем новый массив из 1 и 2. И потом вызываем и возвращаем функцию f1, которую мы описали выше. То есть у нас получается рекурсия функции f1, которая будет идти, пока мы не получим достаточно аргументов.

Теперь наша функция curry может работать с любым количеством аргументов и принимать их любым образом.

Давайте попробуем передавать аргументы по-разному.

curriedAdd(1)(2)(3)
curriedAdd(1)(2, 3)
curriedAdd(1, 2, 3)

Еще раз по шагам, что мы делаем

  1. Мы передаем функцию, которую хотим каррировать в curry и сохраняем в замыкании общее количество аргументов
  2. Мы возвращаем новую функцию f1
  3. Мы вызываем функцию f1 и сравниваем ее аргументы с arity
  4. Если они равны, то мы вызываем исходную функцию с этими аргументами и возвращаем результат
  5. Если мы не получили достаточно аргументов, то мы возвращаем новую функцию f2, получаем аргументы, конкатим их с args, которые хранятся в замыкании и вызываем функцию f1 с этими аргументами
  6. У нас выходит рекурсия, которая продолжается до получения достаточно количества аргументов

Если у вас возникли какие-то вопросы или комментарии, пишите их прямо под этим видео.

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Petr Pozhoga
2 месяцев назад
Здравствуйте, не пойму зачем здесь рекурсия чем плох такой пример ? const curry = (e) => { let arity = e.length return (...args) => { if (args.length >= arity) return e(...args) else return e } } const add = (a,b,c) => a + b + c let curriedAdd = curry(add) console.log(curriedAdd(1, 2, 3))
monsterlessons
2 месяцев назад
Потому что ваш код не покрывает все кейзы. Без рекурсии нельзя писать бесконечный вызов функции Например, curriedAdd(1)(2)(3) выкинет ошибку.
fruity4pie
7 месяцев назад
Спасибо за видео.
monsterlessons
7 месяцев назад
На здоровье
sss
8 месяцев назад
А так можно? Тоже работает let curry = function (fn) { let arity = fn.length; let args = []; return function f(...newArgs) { args = args.concat(newArgs); if(args.length >= arity){ return fn(...args); } return f; }; };
monsterlessons
8 месяцев назад
Можно и так. Без разницы.
GodMerciful
20 дней назад
Попробовал этот пример, и тот что был в уроке. Этот не сработал на примере с масивом объектов, всегда выдавал id первого объекта В чем причина ?)
Ilya Abramov
9 месяцев назад
Можно ли как то написать функцию carry без рекурсии?
monsterlessons
9 месяцев назад
С неограниченным количеством аргументов нельзя.
Максим Маркелов
1год назад назад
У меня вопрос - сейчас нормальная практика не ставить точку с запятой?
monsterlessons
1год назад назад
Я бы сказал, что это зависит от кодстайла команды. Есть такие, где принято ставить, есть где не принято. В javascript существует всего 3 кейса, когда отсутствие точки с запятой поломает ваш код. И обычно на эти 3 кейса люди не попадают, когда пишут код. Я считаю, что точки с запятой - это лишний шум, который мешает читать код.
Александр Анплеенко
1год назад назад
Добрый день, можно по подробнее про 3 кейса когда нужно писать точку с запитой
monsterlessons
1год назад назад
Вот статья на эту тему http://inimino.org/~inimino/blog/javascript_semicolons Вот видео https://www.youtube.com/watch?v=gsfbh17Ax9I Рекомендую видео, так как оно понятнее.