Кариране срещу приложение с частична функция
Като цяло знам, че някои хора понякога бъркат термини. кариране и частично приложение на функцията - използвайте ги взаимозаменяемо, когато не. Това е една от онези теми (като например монадите), които донякъде разбирам и реших, че най-добрият начин да установя знанията си е да пиша за това. Ако прави тази тема по-достъпна за други разработчици, толкова по-добре.
Тази публикация не съдържа Haskell
Почти всички обяснения, които съм виждал по тази тема, дават примери на „правилните“ функционални езици, обикновено Haskell. Нямам нищо против Haskell, просто обикновено ми е по-лесно да разбирам примери на език, който добре познавам. Освен това аз много по-лесно пиши примерите са на такъв език, така че всички примери в тази публикация ще бъдат на C #. Всъщност всички примери са налични в един файл, но няколко променливи в него са преименувани. Просто компилирайте и стартирайте.
C # всъщност не е функционален език - знам достатъчно, за да знам, че делегатите не са пълна заместител на функциите от по-висок ред. Те обаче са достатъчно добри, за да демонстрират описаните принципи.
Въпреки че е възможно да се демонстрира кариране и частично приложение, като се използва функция (метод) с малко аргументи, избрах да използвам три аргумента за яснота. Въпреки че методите ми за извършване на currying и частично приложение ще бъдат общи (така че всички параметри и типове връщане са произволни), за целите на демонстрацията използвам проста функция:
Засега всичко е просто. При този метод няма нищо сложно, не търсете нищо изненадващо в него.
За какво става въпрос?
Както карирането, така и частичното приложение са начини за преобразуване на един вид функция в друг. Ще използваме делегати като приближение на функциите, така че за да работим с метода SampleFunction като стойност, можем да напишем:
Този ред е полезен по две причини:
Сега можем да извикаме делегата с три аргумента:
Или същото:
(Компилаторът C # преобразува първата кратка форма във втората. Генерираният IL ще бъде същият.)
Добре е, ако и трите аргумента са ни достъпни наведнъж, но какво, ако не? За конкретен (макар и донякъде измислен) пример, да предположим, че имаме функция за регистриране с три параметъра (източник, сериозност, съобщение) и в рамките на един клас (който ще нарека BusinessLogic), искаме винаги да използваме една и съща стойност за параметъра " източник ". Искаме да можем лесно да влизаме от всяко място в класа, като посочваме само сериозността и съобщението. Имаме няколко възможности:
- Създайте клас на адаптер, който приема регистрационна функция (или дори обект на регистратор) и стойността на параметъра "source" в своя конструктор, съхранява ги в своите полета и излага метод с два параметъра. Този метод просто делегира повикването на съхранения регистратор, предавайки съхранения "източник" като първи параметър на функцията регистратор. В класа BusinessLogic създаваме екземпляр на адаптера и записваме връзка към него в полето и след това просто извикваме метода с два параметъра, където пожелаем. Може би това е прекалено много, ако се нуждаем само от адаптер от BusinessLogic, но може да се използва повторно ... докато не адаптираме същата функция за регистриране.
- Съхранявайте оригиналния обект на регистратор в нашия клас BusinessLogic, но създайте помощен метод с два параметъра, вътре в който стойността за параметъра източник ще бъде кодирана твърдо. Ако трябва да направим това на няколко места, става досадно.
- Вземете по-функционален подход - в този случай частично приложение на функцията.