Кариране срещу приложение с частична функция

Като цяло знам, че някои хора понякога бъркат термини. кариране и частично приложение на функцията - използвайте ги взаимозаменяемо, когато не. Това е една от онези теми (като например монадите), които донякъде разбирам и реших, че най-добрият начин да установя знанията си е да пиша за това. Ако прави тази тема по-достъпна за други разработчици, толкова по-добре.

Тази публикация не съдържа Haskell

Почти всички обяснения, които съм виждал по тази тема, дават примери на „правилните“ функционални езици, обикновено Haskell. Нямам нищо против Haskell, просто обикновено ми е по-лесно да разбирам примери на език, който добре познавам. Освен това аз много по-лесно пиши примерите са на такъв език, така че всички примери в тази публикация ще бъдат на C #. Всъщност всички примери са налични в един файл, но няколко променливи в него са преименувани. Просто компилирайте и стартирайте.

C # всъщност не е функционален език - знам достатъчно, за да знам, че делегатите не са пълна заместител на функциите от по-висок ред. Те обаче са достатъчно добри, за да демонстрират описаните принципи.

Въпреки че е възможно да се демонстрира кариране и частично приложение, като се използва функция (метод) с малко аргументи, избрах да използвам три аргумента за яснота. Въпреки че методите ми за извършване на currying и частично приложение ще бъдат общи (така че всички параметри и типове връщане са произволни), за целите на демонстрацията използвам проста функция:

Засега всичко е просто. При този метод няма нищо сложно, не търсете нищо изненадващо в него.

За какво става въпрос?

Както карирането, така и частичното приложение са начини за преобразуване на един вид функция в друг. Ще използваме делегати като приближение на функциите, така че за да работим с метода SampleFunction като стойност, можем да напишем:

Този ред е полезен по две причини:

Сега можем да извикаме делегата с три аргумента:

Или същото:

(Компилаторът C # преобразува първата кратка форма във втората. Генерираният IL ще бъде същият.)

Добре е, ако и трите аргумента са ни достъпни наведнъж, но какво, ако не? За конкретен (макар и донякъде измислен) пример, да предположим, че имаме функция за регистриране с три параметъра (източник, сериозност, съобщение) и в рамките на един клас (който ще нарека BusinessLogic), искаме винаги да използваме една и съща стойност за параметъра " източник ". Искаме да можем лесно да влизаме от всяко място в класа, като посочваме само сериозността и съобщението. Имаме няколко възможности:

  • Създайте клас на адаптер, който приема регистрационна функция (или дори обект на регистратор) и стойността на параметъра "source" в своя конструктор, съхранява ги в своите полета и излага метод с два параметъра. Този метод просто делегира повикването на съхранения регистратор, предавайки съхранения "източник" като първи параметър на функцията регистратор. В класа BusinessLogic създаваме екземпляр на адаптера и записваме връзка към него в полето и след това просто извикваме метода с два параметъра, където пожелаем. Може би това е прекалено много, ако се нуждаем само от адаптер от BusinessLogic, но може да се използва повторно ... докато не адаптираме същата функция за регистриране.
  • Съхранявайте оригиналния обект на регистратор в нашия клас BusinessLogic, но създайте помощен метод с два параметъра, вътре в който стойността за параметъра източник ще бъде кодирана твърдо. Ако трябва да направим това на няколко места, става досадно.
  • Вземете по-функционален подход - в този случай частично приложение на функцията.