Na zakończenie mini serii funkcje wyższego rzędu – currying. Dla przypomnienia, funkcje wyższego rzędu to funkcje które albo przyjmują funkcję jak argument, albo zwracają funkcję. Do tej pory mówiliśmy głównie o funkcjach, które przyjmują funkcję jako parametr: Map, Filter, Fold, Reduce. Choć w zeszłych tygodniu wspomnieliśmy o right fold i jej implementacji w C#, która zawracała funkcję.  W tym tygodniu będziemy kontynuować zawracanie funkcji z funkcji w celu zademonstrowania currying.

Z cyklu funkcje wyższego rzędu, do tej pory ukazały się artykuły:

  1. Funkcje wyższego rzędu
  2. Map i Filter
  3. Fold/Reduce
  4. Currying

Currying

Currying to zamiana funkcji przyjmującej wiele argumentów, na serię wywołań funkcji z jednym argumentem. Jednym z najprostszych przykładów jaki można zademonstrować to przykład dodawania w JavaScript. Dla przykładu mamy funkcję add:

function add(x, y) {
    return x + y;
}

add(1, 2);

Przyjmuje ona dwa argumenty, czyli by mieć currying musimy ją tak zamienić by przyjmowała ona tylko jeden argument. Można to uzyskać poprzez zastosowanie funkcji wyższego rzędu – zwrócenie funkcji z funkcji – oraz closure na jednym z parametrów:

function add(x) {
    return function (y) {
        return x + y;
    }
}

add(1)(2);

Co tu taj się dzieje? Po pierwszym wykonaniu funkcji – z 1, tak mniej więcej by wyglądało podstawienie wartości:

function add(1) {
    return function (y) {
        return 1 + y;
    }
}

Czyli dostaniemy z powrotem funkcję która oczekuje jednego argumentu. Dlatego też potem wykorzystujemy (2) by dodać 2 do 1. Dzięki temu możemy stworzyć coś takiego co się zwie partially applied function:

var addOne = add(1);
var addTwo = add(2);
var addThreeToOne = addOne(3); // 4
var addFourToTwo = addTwo(4); // 6

Proste? Jak to się tak rozpisze to jest to banalne.

Dla przykładu implementacja zarówno w C# jak i w F#:

// C#
var add = x => y => x+y;

// F#
let add x = fun y -> x + y

let add = fun x -> fun y -> x + y

let add x  =
   let subf y =
      x + y
   subf

Jak widać funkcję takie można tworzyć na wiele sposobów. W C# też mógłbym ją zapisać na przynajmniej jeszcze jeden lub nawet dwa sposoby. Pół biedy, jak dobrze pamiętam, to w F# wszystkie funkcje wieloargumentowe, są zamieniane przez kompilator na serię wywołań funkcji jednoargumentowych – ale mogę się tutaj mylić.

Po co i kiedy?

Currying można wykorzystać w wielu miejscach, na przykład w takich w których chcemy mieć pre-konfiugrowanlne pewne zachowania. Tak jak z tym dodawaniem, często dodajemy dwa do jakiejś liczby? To tworzymy sobie partial function application:

var addTwoTo = add(2);

Dzięki czemu później notorycznie możemy stosować addTwoTo(number); Dla przykładu, u mnie w jednym z projektów wykorzystuje currying tak:

function date_dt_renderer(format) {
    return function _internal_date_dt_renderer(date) {
        return moment(date).format(format);
    }
}

Która wykorzystuje w następujący sposób w konfiguracji DataTable:

{
    data: 'uploadedDate',
    render: date_dt_renderer(currentFormat)
}

Gdzie wartość z danych o nazwie zostanie przekazana jako parametr do funkcji zwróconej przez date_dr_renderer. W niektórych miejscach w którym format daty nie może ulec zmianie, wykorzystujemy partial function application:

var ie_dt_format = date_dt_renderer('L');

I potem już tylko ie_dt_format jest przekazywany do render.

W innych miejscach stosujemy podobne myki, bardziej lub mniej skomplikowane. Czyli ogólnie to się przydaje.

Podsumowanie

Jak widać, nie taki diabeł straszny jak go malują. Currying mimo iż dziwnie brzmi to jakiś skomplikowany nie jest i bardzo możliwe, że już go wcześniej wykorzystywaliście nawet o tym nie wiedząc. To czy on się wam na pewno przyda to jest zupełnie inne kwestia. Ale prawda jest tak, że każda funkcja wieloargumentowa może zostać zamieniona na currying. Czasami ma to ręce i nogi, czasami nie :)

Tym postem kończę mini serię o funkcjach wyższego rzędu. Mam nadzieję, że wiedza zgromadzona w tych 4 postach wam się przyda :)