----------------------------------------------- Частичное применение ( Partial application ) --------------------------------------------- 00:00:00 Замыкание 00:04:54 Лямбда- функция 00:06:06 Метод bind 00:08:30 Абстрагированный пример функции 00:13:42 Абстрагированный пример функции (расширение) ------------------------------------------ Рекурсивное частичное применение ( Currying ) ------------------------------------------ 00:15:47 Неправильная реализация каррирования 00:22:22 Правильная реализация каррирования 00:28:54 Правильная реализация каррирования (расширение) ------------------------------- Объединение из нескольких функций одной ( Composition ) -------------------------------- 00:31:41 Функциональные абстракции (superposition) 00:38:02 Функциональные абстракции, с сохранением выражений (composition) 00:45:05 Композиция с 2 функциями 00:46:05 Композиция с n - функциями, при помощи метода reduce 00:51:25 Композиция с n - функциями, при помощи цикла 00:54:29 Композиция с n - функциями, при помощи рекурсии 00:57:40 Композиция асинхронных функций ---------------------------------------------------------------------------------------------------------------------------------------------------------------------- 01:01:27 Обобщение
10:10 Говориться что второй аргумент переданный в f11 передается в sum4 как первый аргумент (как 'а'). Но это не совсем так, и если было бы так то вызов с тремя аргументами вместо кидать ошибку, этот третьей аргумент пересылался бы на sum4 как второй аргумент. Так что второй аргумент которого передаем в partial, передается вместо аргумента 'x', а не вместо 'a' у функции sum4. Но когда partial вызываем второй раз, то тогда замкнутый 'x' предается в sum4 как первый аргумент
25:37 Как я понял проблема в том, что когда идет рекурсивный вызов curry в неё передаем IIFE, которая возвращает анонимную функцию (...args2) => ( fn(...args1.concat(args2)) ). Если мы используем rest в функции, то вызов length у такой функции будет возвращать 0. Поэтому мы ограницены только двумя вызовами функции f. Нам надо передать все аргументы за один или два раза. Уже f(1)(2)(3, 4) - не пройдёт. Вот мое решение : const curry = fn => (...args) => ( fn.length > args.length ? curry( fn.bind(null, ...args )) : fn(...args) )
Все просто, я сначала использовал микрофон за $200, но он был очень чувствительный и писался всякий шум, потом блютус гарнитуру за $100 и шумы пропали но звук как из трубы, а теперь взял проводный микрофон за $5 и стало отлично.
Причина не корректной работы curry-bad заключается в том что мы ограничены двумя частичными вызовами и это из за первое выражение которого возвращается из тернарного оператора. А решение очень простая и оно вот так выгладить: const curry = (fn) => (...args) => ( fn.length > args.length ? curry(fn.bind(null, ...args)) : fn(...args) );
в примере 6-curry-bad.js функция curry(((fn, args1) => (...args2) => fn(...args1.concat(args2)) ) )(fn, ...args) может вызваться коректно ровно 1 раз... когда у args1 и у args2 числовые аргументы и в сумме их минимум 4 так как функция sum4 ожидает 4 аргумента - а точнее 4 числа, то вызов fn(...args1.concat(args2)) с 3 или меньше арнументами, заменит нехватку до четырех на undefined, а число или числa + undefined вернет NaN а не функцию с уже присвоенными арнументами а вот в примере 7-curry.js Вы при помощи bind и спред оператора говорите что мол у тебя fn первыми аргументами будут число или масив чисел из ...args1.concat(args2) и возвращаете функцию carry с аргументом fn ( с етими числами) (без имидиат инвокайшен) а при последующем вызове carry eсли аргументов 4 или более - игнорится if и возвращается fn, тоесть sum4 с первыми 4-мя аргументами и результат;
26:27 Вот так проходят варианты 4 и 7 а для 5 го варианта нужно добавить args4. (или ещё раз провести инструкцию сравнение внутри if -а ) const curry = fn => (...args) => { if (fn.length > args.length) { const partial = (fn, ...args1) => (...args2) => (...args3) => fn(...args1.concat(...args2.concat(args3))); return curry(partial)(fn, ...args); } else { return fn(...args); } }; const sum4 = (a, b, c, d) => (a + b + c + d); const f = curry(sum4); const y4 = f(1, 2)(3)(4); //const y5 = f(1)(2)(3)(4); const y7 = f(1)(2)(3, 4); console.log(y4, y7)
Если вдруг кому понадобится функция каррирования функций с любым количеством аргументов, то вот поставил себе как д/з и сделал: function curry(fn){ return function func(...args){ function f(...ar){ return func(...args.concat(ar)); } f.toString = () => fn(...args); return f; } } работает без ограничений, по крайней мере я их не нашёл:)
15:49 функция каррирования реализована крайне неудачно и скорее даже неправильно. В классическом каррировании если количество аргументов передаваемых позже больше или равно, чем количество параметров в функции то функция сразу вызывается, а иначе рекурсивно набирает количество аргументов, а тут шляпа какая то написана)
Правильно ли я понимаю что все отличие каррированной функции от частично-примененной только в том, что каррированная функция это просто преобразованная функция которая может получать аргументы по одному или в случае усовершенствованной версии порциями по одному или по несколько аргументов, а частично-примененная это по сути такая же каррированная функция но с заданным одним или несколькими параметрами? И в принципе как мы привязали эти параметры неважно. То есть например const curriedSum = curry(sum) . Здесь функция curriedSum каррированная. const partialSum = curriedSum(2) А здесь partialSum частично-примененная с параметром 2 . Можно сразу сделать const partialSum = curry(sum)(2). Вопрос в том, зачем тогда нужна функция частичного применения если мы ее можем получить из каррированной функции? Ну и мой вариант каррированной функции: const curry = func => (...args) => args.length >= func.length ? func(...args) : (...args2) => curry(func)(...args, ...args2)
Дня 4 потратил я наверно не меньше чтоб это все осознать и разобраться - но не все конечно понял. Подходы для меня новые - никогда не пользовался карированием.
В примере 6-curry-bad.js если вы напишите вот так "const curry = fn => (...args) => ( fn.length > args.length ? curry( ((fn, ...args1) => (...args2) => (...args3) => ( fn(...args1.concat(args2).concat(...args3)) ))(fn, ...args) ) : fn(...args));", то у вас выполнится функция y4, которая равна f(1, 2)(3)(4), функции y6, y1, y2, и не выполнятся все остальные. Также можно выполнить и функцию y5, добавив еще одну лямбду с аргументами (...args4). То есть придется делать 3 разных варианта функции curry для всех восьми случаев, проверяя каждый раз если одна функция вернула ошибку, то вызвать другую и так далее, но это очень много кода. По сути, это и есть дебаг шестого примера :D
25:40 const curry = fn => (...args) => { if (fn.length > args.length) { const f = (fn, ...args1) => (...args2) => curry(fn)(...args1, ...args2); return curry(f)(fn, ...args); } else { return fn(...args); } } @Timur Shemsedinov, я сдал экзамен?)) Но Вы правы, это мрак, на самом деле, и в сравнении с тем каррированием, где байнд, - оно действительно вообще не читается
slice же не отрезает, а копирует. Тут в связи с упоминаем типа "string" вспомнил вопрос: строка является неизменяемым типом данных в js, при этом работает такое выражение str = str[0].toUpperCase() + str.slice(1). Как в таком случае понимать про неизменяемость строк?
В этом случае берется один символ и берется копия строки без первого символа - это уже две строки из одной получилось. Потом они склеиваются и выходит четвертая.
Пример на 32:10 показывает, что можно писать код в таких ограничениях, что будут только функции и даже операторов, условий и циклов в синтаксисе не останется.
Расскажу про частичное применение из своего опыта. У меня была общая функция, которая считала курс валюты. Она принимала три аргумента - сумму, код валюты, обменный курс. С помощью частичного применения я сделал другие функции, в которые заранее передавал код валюты и текущий курс, чтобы было удобнее считать курс конкретной валюты. То есть если я хотел посчитать сумму в евро, вместо calculateRate(100, 'EUR', 71.12) я писал calculateRateInEUR(100). Такая запись проще для понимания и в ней сложнее ошибиться, передавая аргументы.
Как реализовать подобную функцию: add(1) //1 add(1)(2) //2 let f = add(1)(2) f / 3 f + 3 //6 f(3) //6 console.log(f) // 6 Пробовал переопределить методы valueOf, toString и [Symbol.toPrimitive] работает во всех случаях за исключением "console.log(f) //6"
12:14. Я откладывал этот курс теперь вернулся. Просто смотрю на этот код и мой мозг не понимает даже с объяснениями что здесь происходить я тратил время, но так и не понял что тут происходит. Для кого вы пишете такой трудный код?
Самый большой вопрос вызывает это. На "первом этапе" при вызове const f11 = partial(sum4, 1) тут всё ясно из partial возращается новая функции и не более. Но для меня загадка как работает второй этап const f12 = partial(f11, 2) в ...args возращенной функции попали f11 и 2, x взялся из замыкания, но return fn(x, ...args) сам fn это sum, в 1 аргумент т.е x попадает 1 из замыкания, но что такое аргс как он это отрабатывает ведь там число и функции
Добрый вечер! Спасибо за лекцию. Хочу уточнить один момент. 8:05 Вы говорите, что лямбду нельзя привязать через bind, я попробовал, у меня вышло. Что я делаю не так?) codesandbox.io/s/quizzical-violet-rbyuq
К объекту нельзя привязать лямбду, чтоб потом из нее к this обращаться, а вы ее не привязываете, а делаете частичное применение, что у меня тоже повсеместно есть в примерах.
Это все очень интересно, но вот уже более трех лет программирую, и нигде не видел что бы кто то писал такие штуки. Если кто то знает где найти такой код в опенсорсе, что бы реально посмотреть как это может использоваться, покажите плиз. Спасибо.
Потому что мы сразу в curry, помимо функции fn, передаём (а можем и не передавать) аргументы для применения функции fn На один шаг рекурсии будет меньше если был передан один аргумент для fn Каждый переданный в curry аргумент для функции fn уменьшает дальнейшую рекурсию на 1 шаг Можно вообще передать их столько, сколько требуется fn для выполнения, и рекурсии вообще не будет, сразу выполнится fn(...args)
С одной стороны композируют обычно функции одного аргумента, потому, что любая функция возвращает одно значение, а значит, запуская их по очереди, только первая может быть с несколькими аргументами, а потом все с одним. С этой стороны лучше сначала каррировать, т.е. перейти к одному аргумету, а потом композировать. Но ничего не мешает сделать композицию, когда на вход в первую функцию идет несколько аргументов, и ее уже каррировать. На практике мне обычно эти вещи нужны не вместе.
В 6 примере, на втором шаге рекурсии, fn.length всегда равен 0, т.к. в качестве аргумента curry передается анонимная функция. И, соответственно, тернарное выражение всегда идет по ветке false, выдавая правильный ответ, если каррированная функция была вызвана два раза и ошибку, если более двух раз. И я не нашел ни одного правильного решения в комментариях. Даже рабочие решения, по факту, работают через костыль, а не так как задумывалось.
@@TimurShemsedinov iife в примере был нарочито чудовищный, а Ваш комментарий супер деликатный. Отличный хук ))). Теперь хочется подебажить и потестить это место
Обычные не стрелочные функции нужно использовать только когда они становятся методами и используется this. При одном аргументе скобки писать не нужно. Это елинственно правильный стиль.
Тимур спасибо за лекции, я ваш фанат, написал вот такое каррирование gist.github.com/drummer1992/22c00e19ac8a6403a2969627aa60a6cc это получется, что оно у меня работает без рекурсии?
Тут bind делает то же самое, рекурчия есть, но не явная. Частичное применение можно делать через замыкание или bind, который создает новую функцию. А тут она создается и возвращается, а вызов внаружи, в виде чеининга. Ну и название в примере partial осталось...
Почему функции создаются так как будто библиотеки собираетесь писать x, y, f. Вы поймите одно, когда вы обучаете новичков смысл частичного применения и каррирования заключается главным образом в декларативном названии возвращаемой функции, что крайне обязательно, чтобы потом ее вызвать полноценно. Вы спросите почему я зацепился к этому, отвечаю. Вам любой новичок скажет зачем мне эти лишние телодвижения с отсрочкой вызовов ради одного аргумента или я лучше подожду всех аргументов а потом по человечески вызову. Суть с декларативности еще одном уровне абстракции, который мы получаем названием функции getUser к примеру, а дальше уже по названию функции мы понимаем что она делает. Далее запустим через композицию. То есть мой посыл в том, что не надо писать x и y вы не библиотеку пишите, а обучаете новичков, именно ФП, которая предполагает декларативное названия возвращаем функций
Просто дело привычки, в лиспе вообще (lambda (a b) (+ a b)). В каких-то местах без return даже проще читается, нужно исходить из того, как короче и понятнее. Но "понятнее" - это не понятный термин. Смотря кому "понятнее". Человеку, который привязан к одному языку, многое может быть непонятно, нужно ориентироваться на людей, писавших, как минимум, на 3-4 языках.
@@TimurShemsedinov Я понимаю, что дело привычке. Но я очень редко встречаю лямбды в js сайтов, хотя я их часто ковыряю тк много пишу различные парсеры и тп.
----------------------------------------------- Частичное применение ( Partial application ) ---------------------------------------------
00:00:00 Замыкание
00:04:54 Лямбда- функция
00:06:06 Метод bind
00:08:30 Абстрагированный пример функции
00:13:42 Абстрагированный пример функции (расширение)
------------------------------------------ Рекурсивное частичное применение ( Currying ) ------------------------------------------
00:15:47 Неправильная реализация каррирования
00:22:22 Правильная реализация каррирования
00:28:54 Правильная реализация каррирования (расширение)
------------------------------- Объединение из нескольких функций одной ( Composition ) --------------------------------
00:31:41 Функциональные абстракции (superposition)
00:38:02 Функциональные абстракции, с сохранением выражений (composition)
00:45:05 Композиция с 2 функциями
00:46:05 Композиция с n - функциями, при помощи метода reduce
00:51:25 Композиция с n - функциями, при помощи цикла
00:54:29 Композиция с n - функциями, при помощи рекурсии
00:57:40 Композиция асинхронных функций
----------------------------------------------------------------------------------------------------------------------------------------------------------------------
01:01:27 Обобщение
Шикарная лекция, аж гордость за КПИ берет.
Досмотрел до 3:30 и понял всю суть видео. Хоча достаточно просто прочитать код. Хорошее обьяснение того, зачем применяються замыкания.
Дякую за пояснення та наведені приклади! 🚀
Интересно. Спасибо.
Спасибище за курс
не, ну это невозможно....неделю по пару видео в день смотрю и не могу остановиться. У вас совесть есть?
10:10 Говориться что второй аргумент переданный в f11 передается в sum4 как первый аргумент (как 'а'). Но это не совсем так, и если было бы так то вызов с тремя аргументами вместо кидать ошибку, этот третьей аргумент пересылался бы на sum4 как второй аргумент. Так что второй аргумент которого передаем в partial, передается вместо аргумента 'x', а не вместо 'a' у функции sum4. Но когда partial вызываем второй раз, то тогда замкнутый 'x' предается в sum4 как первый аргумент
очень интересно
25:37
Как я понял проблема в том, что когда идет рекурсивный вызов curry в неё передаем IIFE, которая возвращает анонимную функцию (...args2) => (
fn(...args1.concat(args2)) ). Если мы используем rest в функции, то вызов length у такой функции будет возвращать 0. Поэтому мы ограницены только двумя вызовами функции f. Нам надо передать все аргументы за один или два раза. Уже f(1)(2)(3, 4) - не пройдёт.
Вот мое решение :
const curry = fn => (...args) => (
fn.length > args.length ? curry( fn.bind(null, ...args )) : fn(...args)
)
Звук стал в разы лучше)
Все просто, я сначала использовал микрофон за $200, но он был очень чувствительный и писался всякий шум, потом блютус гарнитуру за $100 и шумы пропали но звук как из трубы, а теперь взял проводный микрофон за $5 и стало отлично.
Спасибо! Путал частичное применение и каррирование
проблема в плохом карировании в том, что concat может соидениять только 2 массива аргументов
Спасибо!
Причина не корректной работы curry-bad заключается в том что мы ограничены двумя частичными вызовами и это из за первое выражение которого возвращается из тернарного оператора. А решение очень простая и оно вот так выгладить:
const curry = (fn) => (...args) => (
fn.length > args.length ?
curry(fn.bind(null, ...args)) :
fn(...args)
);
это же тоже самое, как функция карирования на след слайде :)
в примере 6-curry-bad.js
функция curry(((fn, args1) => (...args2) => fn(...args1.concat(args2)) ) )(fn, ...args) может вызваться коректно ровно 1 раз...
когда у args1 и у args2 числовые аргументы и в сумме их минимум 4
так как функция sum4 ожидает 4 аргумента - а точнее 4 числа,
то вызов fn(...args1.concat(args2)) с 3 или меньше арнументами, заменит нехватку до четырех на undefined,
а число или числa + undefined вернет NaN а не функцию с уже присвоенными арнументами
а вот в примере 7-curry.js
Вы при помощи bind и спред оператора говорите что мол у тебя fn первыми аргументами будут число или масив чисел из ...args1.concat(args2) и возвращаете функцию carry с аргументом fn ( с етими числами) (без имидиат инвокайшен)
а при последующем вызове carry eсли аргументов 4 или более - игнорится if и возвращается fn, тоесть sum4 с первыми 4-мя аргументами и результат;
26:27 Вот так проходят варианты 4 и 7 а для 5 го варианта нужно добавить args4. (или ещё раз провести инструкцию сравнение внутри if -а )
const curry = fn => (...args) => {
if (fn.length > args.length) {
const partial = (fn, ...args1) => (...args2) => (...args3) => fn(...args1.concat(...args2.concat(args3)));
return curry(partial)(fn, ...args);
} else {
return fn(...args);
}
};
const sum4 = (a, b, c, d) => (a + b + c + d);
const f = curry(sum4);
const y4 = f(1, 2)(3)(4);
//const y5 = f(1)(2)(3)(4);
const y7 = f(1)(2)(3, 4);
console.log(y4, y7)
В тестах к задачам на github ошибки.
Если вдруг кому понадобится функция каррирования функций с любым количеством аргументов, то вот поставил себе как д/з и сделал:
function curry(fn){
return function func(...args){
function f(...ar){
return func(...args.concat(ar));
}
f.toString = () => fn(...args);
return f;
}
}
работает без ограничений, по крайней мере я их не нашёл:)
15:49 функция каррирования реализована крайне неудачно и скорее даже неправильно. В классическом каррировании если количество аргументов передаваемых позже больше или равно, чем количество параметров в функции то функция сразу вызывается, а иначе рекурсивно набирает количество аргументов, а тут шляпа какая то написана)
прошу прощения не дослушал)
Правильно ли я понимаю что все отличие каррированной функции от частично-примененной только в том, что каррированная функция это просто преобразованная функция которая может получать аргументы по одному или в случае усовершенствованной версии порциями по одному или по несколько аргументов, а частично-примененная это по сути такая же каррированная функция но с заданным одним или несколькими параметрами? И в принципе как мы привязали эти параметры неважно. То есть например const curriedSum = curry(sum) . Здесь функция curriedSum каррированная. const partialSum = curriedSum(2) А здесь partialSum частично-примененная с параметром 2 . Можно сразу сделать const partialSum = curry(sum)(2). Вопрос в том, зачем тогда нужна функция частичного применения если мы ее можем получить из каррированной функции? Ну и мой вариант каррированной функции: const curry = func => (...args) => args.length >= func.length ? func(...args) : (...args2) => curry(func)(...args, ...args2)
Дня 4 потратил я наверно не меньше чтоб это все осознать и разобраться - но не все конечно понял. Подходы для меня новые - никогда не пользовался карированием.
тоже сижу 3 день😁
Почему, если нужно два массива раздробить на аргументы, используется (...arr1.concat(arr2)), а не (...arr1, ...arr2)?
Примеры 5 и 6.
Да, Вы правы, спасибо, исправил github.com/HowProgrammingWorks/PartialApplication/blob/master/JavaScript/5-partial-ext.js
В примере 6-curry-bad.js если вы напишите вот так "const curry = fn => (...args) => (
fn.length > args.length ? curry(
((fn, ...args1) => (...args2) => (...args3) => (
fn(...args1.concat(args2).concat(...args3))
))(fn, ...args)
) : fn(...args));",
то у вас выполнится функция y4, которая равна f(1, 2)(3)(4), функции y6, y1, y2, и не выполнятся все остальные. Также можно выполнить и функцию y5, добавив еще одну лямбду с аргументами (...args4). То есть придется делать 3 разных варианта функции curry для всех восьми случаев, проверяя каждый раз если одна функция вернула ошибку, то вызвать другую и так далее, но это очень много кода. По сути, это и есть дебаг шестого примера :D
25:40
const curry = fn => (...args) => {
if (fn.length > args.length) {
const f = (fn, ...args1) => (...args2) => curry(fn)(...args1, ...args2);
return curry(f)(fn, ...args);
} else {
return fn(...args);
}
}
@Timur Shemsedinov, я сдал экзамен?))
Но Вы правы, это мрак, на самом деле, и в сравнении с тем каррированием, где байнд, - оно действительно вообще не читается
Видео не полное, композицию обрезали:((
В отдельное видео уехало ua-cam.com/video/xS9FicVrOTI/v-deo.html
@@TimurShemsedinov спасибо !
slice же не отрезает, а копирует. Тут в связи с упоминаем типа "string" вспомнил вопрос: строка является неизменяемым типом данных в js, при этом работает такое выражение str = str[0].toUpperCase() + str.slice(1). Как в таком случае понимать про неизменяемость строк?
В этом случае берется один символ и берется копия строки без первого символа - это уже две строки из одной получилось. Потом они склеиваются и выходит четвертая.
А я что-то не понял, смысл от вот такого скрытия операторов, если они все равно в функции вызываются? Где это можно будет применить?
Пример на 32:10 показывает, что можно писать код в таких ограничениях, что будут только функции и даже операторов, условий и циклов в синтаксисе не останется.
Расскажу про частичное применение из своего опыта. У меня была общая функция, которая считала курс валюты. Она принимала три аргумента - сумму, код валюты, обменный курс. С помощью частичного применения я сделал другие функции, в которые заранее передавал код валюты и текущий курс, чтобы было удобнее считать курс конкретной валюты. То есть если я хотел посчитать сумму в евро, вместо calculateRate(100, 'EUR', 71.12) я писал calculateRateInEUR(100). Такая запись проще для понимания и в ней сложнее ошибиться, передавая аргументы.
@@stephaninabox Читабельность 5+
const carry = (fn) => (...a1) =>
fn.length > a1.length
? (...a2) => carry(fn)(...a1.concat(a2))
: fn(...a1)
Как реализовать подобную функцию:
add(1) //1
add(1)(2) //2
let f = add(1)(2)
f / 3
f + 3 //6
f(3) //6
console.log(f) // 6
Пробовал переопределить методы valueOf, toString и [Symbol.toPrimitive] работает во всех случаях за исключением "console.log(f) //6"
💥💥💥
12:14. Я откладывал этот курс теперь вернулся. Просто смотрю на этот код и мой мозг не понимает даже с объяснениями что здесь происходить я тратил время, но так и не понял что тут происходит. Для кого вы пишете такой трудный код?
Самый большой вопрос вызывает это. На "первом этапе" при вызове const f11 = partial(sum4, 1) тут всё ясно из partial возращается новая функции и не более. Но для меня загадка как работает второй этап const f12 = partial(f11, 2) в ...args возращенной функции попали f11 и 2, x взялся из замыкания, но return fn(x, ...args) сам fn это sum, в 1 аргумент т.е x попадает 1 из замыкания, но что такое аргс как он это отрабатывает ведь там число и функции
Это не трудный код, он просто непривычный, другая парадигма. У меня в лекциях есть разные парадигмы и то, как их совмещать
Добрый вечер! Спасибо за лекцию. Хочу уточнить один момент. 8:05 Вы говорите, что лямбду нельзя привязать через bind, я попробовал, у меня вышло. Что я делаю не так?) codesandbox.io/s/quizzical-violet-rbyuq
К объекту нельзя привязать лямбду, чтоб потом из нее к this обращаться, а вы ее не привязываете, а делаете частичное применение, что у меня тоже повсеместно есть в примерах.
fns.reverse().reduce((args, fn) => [fn(...args)], args); а почему не reduceRight?
Это все очень интересно, но вот уже более трех лет программирую, и нигде не видел что бы кто то писал такие штуки. Если кто то знает где найти такой код в опенсорсе, что бы реально посмотреть как это может использоваться, покажите плиз. Спасибо.
В реакте, например, посмотрите
Так и не понял, почему в 8 примере на один шаг рекурсии меньше
Потому что мы сразу в curry, помимо функции fn, передаём (а можем и не передавать) аргументы для применения функции fn
На один шаг рекурсии будет меньше если был передан один аргумент для fn
Каждый переданный в curry аргумент для функции fn уменьшает дальнейшую рекурсию на 1 шаг
Можно вообще передать их столько, сколько требуется fn для выполнения, и рекурсии вообще не будет, сразу выполнится fn(...args)
Чи можна каррирувати композицію функцій? Чи спочатку потрібно каррирувати всі функції окремо а потім створити композицію?
С одной стороны композируют обычно функции одного аргумента, потому, что любая функция возвращает одно значение, а значит, запуская их по очереди, только первая может быть с несколькими аргументами, а потом все с одним. С этой стороны лучше сначала каррировать, т.е. перейти к одному аргумету, а потом композировать. Но ничего не мешает сделать композицию, когда на вход в первую функцию идет несколько аргументов, и ее уже каррировать. На практике мне обычно эти вещи нужны не вместе.
В 6 примере, на втором шаге рекурсии, fn.length всегда равен 0, т.к. в качестве аргумента curry передается анонимная функция. И, соответственно, тернарное выражение всегда идет по ветке false, выдавая правильный ответ, если каррированная функция была вызвана два раза и ошибку, если более двух раз.
И я не нашел ни одного правильного решения в комментариях. Даже рабочие решения, по факту, работают через костыль, а не так как задумывалось.
Видимо, кроме как через bind, 6 пример не решить. А это, по сути, почти то же самое, что и в 7/8 примерах.
const curry =
(fn) =>
(...args) =>
(fn.length > args.length ? curry(fn.bind(null, ...args)) : fn(...args));
// (1) можно вместе с карированием передавать аргументы
const curryArgs = (fn, ...args2) => (...args) =>
(
fn.length > (args.length + args2.length)
? curryArgs (fn, ...args, ...args2)
: fn (...args, ...args2)
);
// (2) native
const curry = fn => (...args) =>
(
fn.length > args.length
? (...args2) => curry (fn) (...args, ...args2)
: fn (...args)
);
Замечательная лекция. Похоже на троллинг ua-cam.com/video/ND8KQ5xjk7o/v-deo.html ))))
Почему так?
@@TimurShemsedinov iife в примере был нарочито чудовищный, а Ваш комментарий супер деликатный. Отличный хук ))). Теперь хочется подебажить и потестить это место
Лекция понравилась, но фабрики на стрелочных функциях читать очень тяжело, особенно с одним параметром когда вы для краткости отбрасываете скобки.
Обычные не стрелочные функции нужно использовать только когда они становятся методами и используется this. При одном аргументе скобки писать не нужно. Это елинственно правильный стиль.
а лекция как то уменьшилась или или .. ?
Как это уменьшилась?
@@TimurShemsedinov не знаю как, у меня от началаи до конца около 30 минут .. может у меня что то не так ?)
тоже самое наблюдаю. тайминг видео на час, а само видео на полчаса. ровно до композиции функций
@@maximsozinov88 Точно распилили на два видео
@@TimurShemsedinov добрый вечер. а где можно посмотреть вторую часть видео? не могу найти среди других ваших видео лекций, к сожалению
Тимур спасибо за лекции, я ваш фанат,
написал вот такое каррирование
gist.github.com/drummer1992/22c00e19ac8a6403a2969627aa60a6cc
это получется, что оно у меня работает без рекурсии?
Тут bind делает то же самое, рекурчия есть, но не явная. Частичное применение можно делать через замыкание или bind, который создает новую функцию. А тут она создается и возвращается, а вызов внаружи, в виде чеининга. Ну и название в примере partial осталось...
@@TimurShemsedinov Термин неявная рекурсия для меня новый))
Спасибо за ответ!
@@Andrey-qf8uw еще косвенная рекурсия называют, например, если функция f вызывает g, а g вызывает f. Тогда можно сразу и не заметить.
Timur Shemsedinov Спасибо 🙏🏻
Почему функции создаются так как будто библиотеки собираетесь писать x, y, f. Вы поймите одно, когда вы обучаете новичков смысл частичного применения и каррирования заключается главным образом в декларативном названии возвращаемой функции, что крайне обязательно, чтобы потом ее вызвать полноценно. Вы спросите почему я зацепился к этому, отвечаю. Вам любой новичок скажет зачем мне эти лишние телодвижения с отсрочкой вызовов ради одного аргумента или я лучше подожду всех аргументов а потом по человечески вызову. Суть с декларативности еще одном уровне абстракции, который мы получаем названием функции getUser к примеру, а дальше уже по названию функции мы понимаем что она делает. Далее запустим через композицию.
То есть мой посыл в том, что не надо писать x и y вы не библиотеку пишите, а обучаете новичков, именно ФП, которая предполагает декларативное названия возвращаем функций
Ох не люблю я эти стрелочки без return мозг закипает, пока поймешь что куда.
Просто дело привычки, в лиспе вообще (lambda (a b) (+ a b)). В каких-то местах без return даже проще читается, нужно исходить из того, как короче и понятнее. Но "понятнее" - это не понятный термин. Смотря кому "понятнее". Человеку, который привязан к одному языку, многое может быть непонятно, нужно ориентироваться на людей, писавших, как минимум, на 3-4 языках.
@@TimurShemsedinov Я понимаю, что дело привычке. Но я очень редко встречаю лямбды в js сайтов, хотя я их часто ковыряю тк много пишу различные парсеры и тп.
@@SilverStormAndGoldenRain ну так js прогоняют через babel там, например...
const curry = fn => {
const res = ( ...args ) =>
fn.length > args.length ? (...rest) => res(...args, ...rest ) : fn(...args )
return res;
}
Если кому интересен ответ на 6-curry-bad.js - сделал так:
const curry = fn => (...args) => (
fn.length > args.length ? curry(
((fn, ...args1) => (...args2) => (
curry(fn)(...args1.concat(args2))
))(fn, ...args)
) : fn(...args)
);
Спасибо!