Частичное применение и каррирование в JavaScript

Поділитися
Вставка
  • Опубліковано 29 гру 2024

КОМЕНТАРІ • 82

  • @anatolykobzisty9827
    @anatolykobzisty9827 5 років тому +34

    ----------------------------------------------- Частичное применение ( 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 Обобщение

  • @cassinid8843
    @cassinid8843 5 років тому +32

    Шикарная лекция, аж гордость за КПИ берет.

  • @Andriy8075
    @Andriy8075 Рік тому +1

    Досмотрел до 3:30 и понял всю суть видео. Хоча достаточно просто прочитать код. Хорошее обьяснение того, зачем применяються замыкания.

  • @TimaGixe
    @TimaGixe Рік тому +1

    Дякую за пояснення та наведені приклади! 🚀

  • @artem_travlo
    @artem_travlo Рік тому +1

    Интересно. Спасибо.

  • @DimitarRad
    @DimitarRad 2 роки тому +1

    Спасибище за курс

  • @СтройС-я9о
    @СтройС-я9о 2 роки тому +1

    не, ну это невозможно....неделю по пару видео в день смотрю и не могу остановиться. У вас совесть есть?

  • @TimurSevimli
    @TimurSevimli 2 роки тому

    10:10 Говориться что второй аргумент переданный в f11 передается в sum4 как первый аргумент (как 'а'). Но это не совсем так, и если было бы так то вызов с тремя аргументами вместо кидать ошибку, этот третьей аргумент пересылался бы на sum4 как второй аргумент. Так что второй аргумент которого передаем в partial, передается вместо аргумента 'x', а не вместо 'a' у функции sum4. Но когда partial вызываем второй раз, то тогда замкнутый 'x' предается в sum4 как первый аргумент

  • @poedgar977
    @poedgar977 5 років тому +3

    очень интересно

  • @buksirchik1663
    @buksirchik1663 4 роки тому +1

    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)
    )

  • @dimon_torpeda
    @dimon_torpeda 6 років тому +11

    Звук стал в разы лучше)

    • @TimurShemsedinov
      @TimurShemsedinov  6 років тому +46

      Все просто, я сначала использовал микрофон за $200, но он был очень чувствительный и писался всякий шум, потом блютус гарнитуру за $100 и шумы пропали но звук как из трубы, а теперь взял проводный микрофон за $5 и стало отлично.

  • @ИльяБондаренко-т4е

    Спасибо! Путал частичное применение и каррирование

  • @ДаніілДенисюк-р4ь
    @ДаніілДенисюк-р4ь 6 років тому +8

    проблема в плохом карировании в том, что concat может соидениять только 2 массива аргументов

  • @Antonio-fm1sq
    @Antonio-fm1sq 3 роки тому +1

    Спасибо!

  • @TimurSevimli
    @TimurSevimli 2 роки тому +1

    Причина не корректной работы curry-bad заключается в том что мы ограничены двумя частичными вызовами и это из за первое выражение которого возвращается из тернарного оператора. А решение очень простая и оно вот так выгладить:
    const curry = (fn) => (...args) => (
    fn.length > args.length ?
    curry(fn.bind(null, ...args)) :
    fn(...args)
    );

    • @nikitapavlenko8303
      @nikitapavlenko8303 Рік тому

      это же тоже самое, как функция карирования на след слайде :)

  • @igorizbish1072
    @igorizbish1072 3 роки тому

    в примере 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-мя аргументами и результат;

  • @ziyadseykhanov3967
    @ziyadseykhanov3967 5 років тому

    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)

  • @dev-zb9mg
    @dev-zb9mg Місяць тому

    В тестах к задачам на github ошибки.

  • @exeleNt861
    @exeleNt861 5 років тому +2

    Если вдруг кому понадобится функция каррирования функций с любым количеством аргументов, то вот поставил себе как д/з и сделал:
    function curry(fn){
    return function func(...args){
    function f(...ar){
    return func(...args.concat(ar));
    }
    f.toString = () => fn(...args);
    return f;
    }
    }
    работает без ограничений, по крайней мере я их не нашёл:)

  • @boycovclub
    @boycovclub 2 місяці тому

    15:49 функция каррирования реализована крайне неудачно и скорее даже неправильно. В классическом каррировании если количество аргументов передаваемых позже больше или равно, чем количество параметров в функции то функция сразу вызывается, а иначе рекурсивно набирает количество аргументов, а тут шляпа какая то написана)

    • @boycovclub
      @boycovclub 2 місяці тому

      прошу прощения не дослушал)

  • @ДенисЕгоров-щ1д
    @ДенисЕгоров-щ1д 2 роки тому

    Правильно ли я понимаю что все отличие каррированной функции от частично-примененной только в том, что каррированная функция это просто преобразованная функция которая может получать аргументы по одному или в случае усовершенствованной версии порциями по одному или по несколько аргументов, а частично-примененная это по сути такая же каррированная функция но с заданным одним или несколькими параметрами? И в принципе как мы привязали эти параметры неважно. То есть например 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)

  • @dmytrohaponov5517
    @dmytrohaponov5517 3 роки тому +5

    Дня 4 потратил я наверно не меньше чтоб это все осознать и разобраться - но не все конечно понял. Подходы для меня новые - никогда не пользовался карированием.

    • @maks4941
      @maks4941 Рік тому +1

      тоже сижу 3 день😁

  • @АндрейКотомкин-о9ю
    @АндрейКотомкин-о9ю 5 років тому +5

    Почему, если нужно два массива раздробить на аргументы, используется (...arr1.concat(arr2)), а не (...arr1, ...arr2)?
    Примеры 5 и 6.

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +4

      Да, Вы правы, спасибо, исправил github.com/HowProgrammingWorks/PartialApplication/blob/master/JavaScript/5-partial-ext.js

  • @delimobilstories6781
    @delimobilstories6781 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

  • @alexey4102
    @alexey4102 4 роки тому

    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, я сдал экзамен?))
    Но Вы правы, это мрак, на самом деле, и в сравнении с тем каррированием, где байнд, - оно действительно вообще не читается

  • @mykhailonikolaiev6512
    @mykhailonikolaiev6512 3 роки тому +2

    Видео не полное, композицию обрезали:((

    • @TimurShemsedinov
      @TimurShemsedinov  3 роки тому +2

      В отдельное видео уехало ua-cam.com/video/xS9FicVrOTI/v-deo.html

    • @mykhailonikolaiev6512
      @mykhailonikolaiev6512 3 роки тому

      @@TimurShemsedinov спасибо !

  • @igorsavelev9013
    @igorsavelev9013 5 років тому +1

    slice же не отрезает, а копирует. Тут в связи с упоминаем типа "string" вспомнил вопрос: строка является неизменяемым типом данных в js, при этом работает такое выражение str = str[0].toUpperCase() + str.slice(1). Как в таком случае понимать про неизменяемость строк?

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +3

      В этом случае берется один символ и берется копия строки без первого символа - это уже две строки из одной получилось. Потом они склеиваются и выходит четвертая.

  • @gaddyya
    @gaddyya 5 років тому +2

    А я что-то не понял, смысл от вот такого скрытия операторов, если они все равно в функции вызываются? Где это можно будет применить?

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому

      Пример на 32:10 показывает, что можно писать код в таких ограничениях, что будут только функции и даже операторов, условий и циклов в синтаксисе не останется.

    • @stephaninabox
      @stephaninabox 5 років тому +5

      Расскажу про частичное применение из своего опыта. У меня была общая функция, которая считала курс валюты. Она принимала три аргумента - сумму, код валюты, обменный курс. С помощью частичного применения я сделал другие функции, в которые заранее передавал код валюты и текущий курс, чтобы было удобнее считать курс конкретной валюты. То есть если я хотел посчитать сумму в евро, вместо calculateRate(100, 'EUR', 71.12) я писал calculateRateInEUR(100). Такая запись проще для понимания и в ней сложнее ошибиться, передавая аргументы.

    • @ВладимирЗуев-м5к
      @ВладимирЗуев-м5к 4 роки тому

      @@stephaninabox Читабельность 5+

  • @sfmict
    @sfmict 5 років тому +2

    const carry = (fn) => (...a1) =>
    fn.length > a1.length
    ? (...a2) => carry(fn)(...a1.concat(a2))
    : fn(...a1)

  • @unev
    @unev 3 роки тому

    Как реализовать подобную функцию:
    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"

  • @jamjam3337
    @jamjam3337 2 роки тому

    💥💥💥

  • @ЕвгенийКраев-я2ж
    @ЕвгенийКраев-я2ж 3 роки тому

    12:14. Я откладывал этот курс теперь вернулся. Просто смотрю на этот код и мой мозг не понимает даже с объяснениями что здесь происходить я тратил время, но так и не понял что тут происходит. Для кого вы пишете такой трудный код?

    • @ЕвгенийКраев-я2ж
      @ЕвгенийКраев-я2ж 3 роки тому

      Самый большой вопрос вызывает это. На "первом этапе" при вызове const f11 = partial(sum4, 1) тут всё ясно из partial возращается новая функции и не более. Но для меня загадка как работает второй этап const f12 = partial(f11, 2) в ...args возращенной функции попали f11 и 2, x взялся из замыкания, но return fn(x, ...args) сам fn это sum, в 1 аргумент т.е x попадает 1 из замыкания, но что такое аргс как он это отрабатывает ведь там число и функции

    • @TimurShemsedinov
      @TimurShemsedinov  3 роки тому

      Это не трудный код, он просто непривычный, другая парадигма. У меня в лекциях есть разные парадигмы и то, как их совмещать

  • @andriikonstantynov4508
    @andriikonstantynov4508 5 років тому

    Добрый вечер! Спасибо за лекцию. Хочу уточнить один момент. 8:05 Вы говорите, что лямбду нельзя привязать через bind, я попробовал, у меня вышло. Что я делаю не так?) codesandbox.io/s/quizzical-violet-rbyuq

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +1

      К объекту нельзя привязать лямбду, чтоб потом из нее к this обращаться, а вы ее не привязываете, а делаете частичное применение, что у меня тоже повсеместно есть в примерах.

  • @КонстантинГоршков-я9д

    fns.reverse().reduce((args, fn) => [fn(...args)], args); а почему не reduceRight?

  • @yuripalienko6259
    @yuripalienko6259 4 роки тому +1

    Это все очень интересно, но вот уже более трех лет программирую, и нигде не видел что бы кто то писал такие штуки. Если кто то знает где найти такой код в опенсорсе, что бы реально посмотреть как это может использоваться, покажите плиз. Спасибо.

    • @TimurShemsedinov
      @TimurShemsedinov  3 роки тому

      В реакте, например, посмотрите

  • @sunrisegc1026
    @sunrisegc1026 5 років тому +1

    Так и не понял, почему в 8 примере на один шаг рекурсии меньше

    • @alexey4102
      @alexey4102 4 роки тому

      Потому что мы сразу в curry, помимо функции fn, передаём (а можем и не передавать) аргументы для применения функции fn
      На один шаг рекурсии будет меньше если был передан один аргумент для fn
      Каждый переданный в curry аргумент для функции fn уменьшает дальнейшую рекурсию на 1 шаг
      Можно вообще передать их столько, сколько требуется fn для выполнения, и рекурсии вообще не будет, сразу выполнится fn(...args)

  • @dimapetruniak6263
    @dimapetruniak6263 6 років тому +1

    Чи можна каррирувати композицію функцій? Чи спочатку потрібно каррирувати всі функції окремо а потім створити композицію?

    • @TimurShemsedinov
      @TimurShemsedinov  6 років тому

      С одной стороны композируют обычно функции одного аргумента, потому, что любая функция возвращает одно значение, а значит, запуская их по очереди, только первая может быть с несколькими аргументами, а потом все с одним. С этой стороны лучше сначала каррировать, т.е. перейти к одному аргумету, а потом композировать. Но ничего не мешает сделать композицию, когда на вход в первую функцию идет несколько аргументов, и ее уже каррировать. На практике мне обычно эти вещи нужны не вместе.

  • @alexeyrendot2458
    @alexeyrendot2458 2 роки тому

    В 6 примере, на втором шаге рекурсии, fn.length всегда равен 0, т.к. в качестве аргумента curry передается анонимная функция. И, соответственно, тернарное выражение всегда идет по ветке false, выдавая правильный ответ, если каррированная функция была вызвана два раза и ошибку, если более двух раз.
    И я не нашел ни одного правильного решения в комментариях. Даже рабочие решения, по факту, работают через костыль, а не так как задумывалось.

    • @alexeyrendot2458
      @alexeyrendot2458 2 роки тому

      Видимо, кроме как через bind, 6 пример не решить. А это, по сути, почти то же самое, что и в 7/8 примерах.

    • @alexeyrendot2458
      @alexeyrendot2458 2 роки тому

      const curry =
      (fn) =>
      (...args) =>
      (fn.length > args.length ? curry(fn.bind(null, ...args)) : fn(...args));

  • @O1L2E3G
    @O1L2E3G 4 роки тому

    // (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)
    );

  • @kurochkin1
    @kurochkin1 Рік тому

    Замечательная лекция. Похоже на троллинг ua-cam.com/video/ND8KQ5xjk7o/v-deo.html ))))

    • @TimurShemsedinov
      @TimurShemsedinov  Рік тому

      Почему так?

    • @kurochkin1
      @kurochkin1 Рік тому

      @@TimurShemsedinov iife в примере был нарочито чудовищный, а Ваш комментарий супер деликатный. Отличный хук ))). Теперь хочется подебажить и потестить это место

  • @Boris1990ua
    @Boris1990ua 4 роки тому

    Лекция понравилась, но фабрики на стрелочных функциях читать очень тяжело, особенно с одним параметром когда вы для краткости отбрасываете скобки.

    • @TimurShemsedinov
      @TimurShemsedinov  4 роки тому

      Обычные не стрелочные функции нужно использовать только когда они становятся методами и используется this. При одном аргументе скобки писать не нужно. Это елинственно правильный стиль.

  • @ИванПетров-б8в6щ
    @ИванПетров-б8в6щ 3 роки тому

    а лекция как то уменьшилась или или .. ?

    • @TimurShemsedinov
      @TimurShemsedinov  3 роки тому

      Как это уменьшилась?

    • @ИванПетров-б8в6щ
      @ИванПетров-б8в6щ 3 роки тому

      @@TimurShemsedinov не знаю как, у меня от началаи до конца около 30 минут .. может у меня что то не так ?)

    • @maximsozinov88
      @maximsozinov88 3 роки тому +1

      тоже самое наблюдаю. тайминг видео на час, а само видео на полчаса. ровно до композиции функций

    • @TimurShemsedinov
      @TimurShemsedinov  3 роки тому

      @@maximsozinov88 Точно распилили на два видео

    • @ML-ey5qc
      @ML-ey5qc 3 роки тому

      @@TimurShemsedinov добрый вечер. а где можно посмотреть вторую часть видео? не могу найти среди других ваших видео лекций, к сожалению

  • @Andrey-qf8uw
    @Andrey-qf8uw 5 років тому +1

    Тимур спасибо за лекции, я ваш фанат,
    написал вот такое каррирование
    gist.github.com/drummer1992/22c00e19ac8a6403a2969627aa60a6cc
    это получется, что оно у меня работает без рекурсии?

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +1

      Тут bind делает то же самое, рекурчия есть, но не явная. Частичное применение можно делать через замыкание или bind, который создает новую функцию. А тут она создается и возвращается, а вызов внаружи, в виде чеининга. Ну и название в примере partial осталось...

    • @Andrey-qf8uw
      @Andrey-qf8uw 5 років тому +1

      @@TimurShemsedinov Термин неявная рекурсия для меня новый))
      Спасибо за ответ!

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +3

      @@Andrey-qf8uw еще косвенная рекурсия называют, например, если функция f вызывает g, а g вызывает f. Тогда можно сразу и не заметить.

    • @Andrey-qf8uw
      @Andrey-qf8uw 5 років тому +3

      Timur Shemsedinov Спасибо 🙏🏻

  • @boycovclub
    @boycovclub 2 місяці тому

    Почему функции создаются так как будто библиотеки собираетесь писать x, y, f. Вы поймите одно, когда вы обучаете новичков смысл частичного применения и каррирования заключается главным образом в декларативном названии возвращаемой функции, что крайне обязательно, чтобы потом ее вызвать полноценно. Вы спросите почему я зацепился к этому, отвечаю. Вам любой новичок скажет зачем мне эти лишние телодвижения с отсрочкой вызовов ради одного аргумента или я лучше подожду всех аргументов а потом по человечески вызову. Суть с декларативности еще одном уровне абстракции, который мы получаем названием функции getUser к примеру, а дальше уже по названию функции мы понимаем что она делает. Далее запустим через композицию.
    То есть мой посыл в том, что не надо писать x и y вы не библиотеку пишите, а обучаете новичков, именно ФП, которая предполагает декларативное названия возвращаем функций

  • @SilverStormAndGoldenRain
    @SilverStormAndGoldenRain 5 років тому +1

    Ох не люблю я эти стрелочки без return мозг закипает, пока поймешь что куда.

    • @TimurShemsedinov
      @TimurShemsedinov  5 років тому +2

      Просто дело привычки, в лиспе вообще (lambda (a b) (+ a b)). В каких-то местах без return даже проще читается, нужно исходить из того, как короче и понятнее. Но "понятнее" - это не понятный термин. Смотря кому "понятнее". Человеку, который привязан к одному языку, многое может быть непонятно, нужно ориентироваться на людей, писавших, как минимум, на 3-4 языках.

    • @SilverStormAndGoldenRain
      @SilverStormAndGoldenRain 5 років тому +4

      @@TimurShemsedinov Я понимаю, что дело привычке. Но я очень редко встречаю лямбды в js сайтов, хотя я их часто ковыряю тк много пишу различные парсеры и тп.

    • @Eternal_Rise
      @Eternal_Rise 5 років тому +6

      @@SilverStormAndGoldenRain ну так js прогоняют через babel там, например...

  • @СергейТурин-д4б
    @СергейТурин-д4б 10 місяців тому

    const curry = fn => {
    const res = ( ...args ) =>
    fn.length > args.length ? (...rest) => res(...args, ...rest ) : fn(...args )
    return res;
    }

  • @ForgottenPirat
    @ForgottenPirat 3 роки тому

    Если кому интересен ответ на 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)
    );

  • @yuriykolegin5261
    @yuriykolegin5261 Рік тому +1

    Спасибо!