Jason. Cała informatyka w jednym miejscu!

Wiecie już jak wygląda wyrażenie lambda, jak je stosować i na co trzeba uważać. W tym artykule dowiecie się szczegółów w jaki sposób można wykorzystać pełen potencjał anonimowych funkcji. Widoczny jest w chwili, kiedy tworzona jest funkcja wyższego rzędu w języku Kotlin! Jeśli ciężko Wam jest to sobie wyobrazić, to zapraszam z podwójnie większym zaangażowaniem!

FUNKCJA WYŻSZEGO RZĘDU ZASKAKUJE SAMĄ SOBĄ!

Bezpiecznie będzie najpierw to wytłumaczyć rozbrajając termin "funkcja wyższego rzędu" tak, jakby był przekładany "na ludzki język". Kiedy mowa o funkcji wyższego rzędu, chodzi o to iż funkcja posiada w sobie wyrażenie lambda w charakterze parametru formalnego, bądź zwraca wyrażenie lambda. Z tego co wiem, nie ma żadnych uwarunkowań co do liczby i typu parametrów kiedy funkcja nie jest funkcją wyższego rzędu. Tu jest prosta zasada. Posiada lambdę albo ją zwraca? Jest funkcją wyższego rzędu! Koniec, kropka.

Przedstawię Wam teraz dwie funkcje różniące się miejscem występowania lambdy: taka, która przyjmuje wyrażenie lambda za parametr i taka, która zwraca takie wyrażenie.

FUNKCJA PRZYJMUJĄCA LAMBDĘ ZA PARAMETR

Pierwszy rodzaj funkcji to wymagający bloku instrukcji jako parametru. Definiujemy funkcję tak samo, jak poprzednio z tym, że teraz za parametr podajemy lambdę. Jak to zrobić?

fun doSomething(operations: () -> Unit) {
	operations()
}

Wystarczy przypomnieć sobie jak prawidłowo określić parametr formalny dla funkcji oraz typ funkcyjny dla lambdy. Powyższy przykład prezentuje jeden parametr będący wyrażeniem lambda, przez co będzie to funkcja wyższego rzędu. Jest to lambda nieposiadająca żadnych parametrów i niezwracająca niczego (typ "Unit").

Popatrzcie za to na wnętrze funkcji. Znajduje się w nim wywołanie naszej lambdy! Napiszę więcej, nic nie stoi na przeszkodzie, żeby wraz z nią dołożyć własne instrukcje już nam bardziej znane, na przykład "println". Nie ma problemu!

fun doSomething(operations: () -> Unit) {
	operations()
	println("Po wywołaniu wyrażenia lambda")
}

Zatem wykona się wyrażenie lambda, a potem wypisanie tekstu na strumień wyjściowy.

A jak wywołanie? Na przykład tak:

doSomething({
	val x = 80
	
	println("Wartością x jest $x.")
})
WYWOŁANIE BEZ NAWIASÓW?!

Zanim przejdziemy do drugiej części artykułu, podzielę się z Wami niekonwencjonalnym zapisem, który można wykonać w stosunku do sytuacji, w której funkcja wyższego rzędu jest wywoływana. Jeżeli dany parametr jest wyrażeniem lambda, możemy zdefiniować go poza nawiasami. Czyli w podanym przykładzie możecie zapisać tak jak jest na górze, albo w taki sposób:

doSomething {
	val x = 80
	
	println("Wartością x jest $x.")
}

Pomijamy kompletnie parę nawiasów! Można tak? W przypadku lambd, tak.

Chwila! Jeszcze przypadek kiedy jest inny parametr niż tylko lambda:

doSomething(5.0) {println("Moja lambda poza nawiasami parametrów!")}

Wtedy nadal piszemy nawiasy, ale możemy "wyjąć" lambdę z ich wnętrza. Innymi słowy, wszystkie parametry niebędące lambdami, muszą dalej "przesiadywać" wewnątrz nawiasów. Jest jeszcze jedno ograniczenie. To zadziała tylko i wyłącznie, kiedy lambda jest ostatnia w kolejności!

Ktoś może spytać "co mi to może dać"? Jeszcze większy skrót zapisu naszych intencji! A tak na serio, taki skrótowiec może bardzo przypominać zapis, jakby etykieta naszej funkcji wyższego rzędu była słowem kluczowym i wymagała klamerek z instrukcjami. Pamiętacie "try"? To już macie wizualizację. Ale dla podkreślenia wagi sytuacji (zakładając, że funkcja wyższego rzędu przyjmuje tylko jeden parametr w postaci wyrażenia lambda):

doSomething {
	println("Moja lambda poza nawiasami parametrów!")
}

Gotowe. Teraz już wiecie co miałem na myśli. Wygląda to tak, jakby to było słowo kluczowe z klamerkami. Uczulam jak cholera, klamra otwierająca musi być w tej samej linii, co wywołanie. Inaczej, nie zostanie to potraktowane jak wywołanie metody, ale jako błąd też nie (w przeciwieństwie do "let")! Tutaj wyjątkowo nie! Na to też uważajcie!

FUNKCJA ZWRACAJĄCA LAMBDĘ

Drugi typ także charakteryzuje się występowaniem wyrażenia lambda, ale tym razem jest ona zwracana jako wynik funkcji. Jestem w stanie zrozumieć, że ciężko można to sobie wyobrazić jak można dosłownie "zwrócić kod". Chwytajcie przykład:

fun getLambda(): (Double) -> Double {
	return { it }
}

Aby nie pomieszało się w głowie, na sam początek banalny przykład. Bardzo prosty blok instrukcji jaki się teraz zwraca to sama wartość typu "Double" bez domieszek.

A teraz użycie "z głową". Pamiętacie przykład z konwersją walut? To patrzcie teraz na pełen potencjał:

fun plnToForeignCurrency(pln: Double, currency: String): (Double) -> Double {
	when (currency) {
		"USD" -> return { it / 4.0710 }
		"Euro" -> return { it / 4.5941 }
		else -> return { it }
	}
}

Wyjątkowo "kolorowa" funkcja. Jest "when", jest zwracanie lambdy. Cały czas zwracam uwagę, że teraz zwracamy KOD, nie wartość, nawet jeśli składa się z pojedynczej instrukcji! Dołożona jest do tego jeszcze jedna funkcja wyższego rzędu:

fun convertPLNToForeignCurrency(pln: Double, conversion: (Double) -> Double) {
	val result = conversion(pln)

	println("PLN po przeliczeniu na obcą walutę: $result")
}

oraz wywołanie po wpleceniu jednego w drugie:

convertPLNToForeignCurrency(25.6, plnToForeignCurrency("Euro"))

i oto ukazuje się proste zaprogramowanie przelicznika naszych polskich złotych na obcą walutę. Celowo dodałem tylko dwie, aby nie zasłaniać istoty tematu. Co się tu w ogóle dzieje? Po kolei, "step by step":

  1. Po przyjęciu parametrów, następuje najpierw wywołanie metody "plnToForeignCurrency", gdyż trzeba ustalić KOD jaki musi później być wykonany.
  2. Program "wchodzi" do funkcji "plnToForeignCurrency" i zagląda do instrukcji wielokrotnego wyboru "when" w poszukiwaniu zgodności pomiędzy literałem zawartym w środku, a literałem zawartym w parametrze aktualnym
  3. W zależności od tego, czy natrafiono na wbudowany literał, czy nie, funkcja zwraca stosowny kod; następuje wyjście z tej funkcji
  4. Program "wraca" do wywołania funkcji "convertPLNToForeignCurrency" i dopiero teraz ją uruchamia
  5. W środku funkcji "convertPLNToForeignCurrency", odbywa się ustalenie wartości zmiennej "result" poprzez wykonanie ZWRÓCONEGO WCZEŚNIEJ KODU, to jest bardzo ważne (np. dla "USD" zostanie wykonany blok "podpięty" pod "USD" i tak dalej) i finalne zwrócenie wartości!
  6. Funkcja wypisuje na strumieniu wyjściowym komunikat ile wyszło w podanej walucie po przeliczeniu ze złotówek

Już widzicie jak wyrażenie lambda potrafi stanowić potężną "broń" w rękach programisty. Na zakończenie wspomnę o jednym. Oczywiście, że można by poprzestać na zwróceniu samej wartości w tym "when" i śluz (np. zwrócenie samej skonwertowanej wartości na dolary amerykańskie). Tylko zaimplementowanie bloków kodu, pozwala na wstawienie wewnątrz dodatkowych instrukcji, na przykład komunikatu "Przeliczam na dolary amerykańskie" (zaraz przed wyrażeniem ze słowem "it") i tak dalej.

Funkcja wyższego rzędu w języku Kotlin

Funkcja wyższego rzędu w języku Kotlin to funkcja wykorzystująca w sobie wyrażenie lambda. Może ją zwracać albo przyjmować za parametr formalny.


Dopiero teraz, po niniejszym wpisie, możecie zobaczyć jak daleko mogą posunąć się Wasze możliwości programowania uniwersalnego po zastosowaniu wyrażenia lambda. Podczas wywoływania funkcji wyższego rzędu, macie pełne prawo wstawić nie samą wartość, lecz treść instrukcji która może się różnić przy każdym wywołaniu. W połączeniu z przyjmowaniem i zwracaniem samych bloków kodu, możliwości rosną Wam OGROMNIE!

PODOBNE ARTYKUŁY