#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. У нас выходит рекурсия, которая продолжается до получения достаточно количества аргументов

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

Только зарегистрированные пользователи могут оставлять комментарии.  Войдите, пожалуйста.
Pavel Aleksandrov
3 месяцев назад
А если я толком не понимаю для чего это нужно - это приговор?)
Petr Pozhoga
9 месяцев назад
Здравствуйте, не пойму зачем здесь рекурсия чем плох такой пример ? 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
9 месяцев назад
Потому что ваш код не покрывает все кейзы. Без рекурсии нельзя писать бесконечный вызов функции Например, curriedAdd(1)(2)(3) выкинет ошибку.
fruity4pie
1год назад назад
Спасибо за видео.
monsterlessons
1год назад назад
На здоровье
sss
1год назад назад
А так можно? Тоже работает 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
1год назад назад
Можно и так. Без разницы.
GodMerciful
8 месяцев назад
Попробовал этот пример, и тот что был в уроке. Этот не сработал на примере с масивом объектов, всегда выдавал id первого объекта В чем причина ?)
Ilya Abramov
1год назад назад
Можно ли как то написать функцию carry без рекурсии?
monsterlessons
1год назад назад
С неограниченным количеством аргументов нельзя.
Максим Маркелов
2 лет назад
У меня вопрос - сейчас нормальная практика не ставить точку с запятой?
monsterlessons
2 лет назад
Я бы сказал, что это зависит от кодстайла команды. Есть такие, где принято ставить, есть где не принято. В javascript существует всего 3 кейса, когда отсутствие точки с запятой поломает ваш код. И обычно на эти 3 кейса люди не попадают, когда пишут код. Я считаю, что точки с запятой - это лишний шум, который мешает читать код.
Александр Анплеенко
2 лет назад
Добрый день, можно по подробнее про 3 кейса когда нужно писать точку с запитой
monsterlessons
2 лет назад
Вот статья на эту тему http://inimino.org/~inimino/blog/javascript_semicolons Вот видео https://www.youtube.com/watch?v=gsfbh17Ax9I Рекомендую видео, так как оно понятнее.