1. Poprzedni: Rozdział 1
Kotlin: ilustrowany przewodnik • Rozdział 2

Funkcje

Chapter cover image

W poprzednim rozdziale napisaliśmy kod w Kotlinie do liczenia obwodu okręgu. W tym rozdziale napiszemy funkcje umożliwiające w prosty sposób obliczenie obwodu dowolnego okręgu!

Funkcje

Jak widzieliśmy w poprzednim rozdziale, obliczenie obwodu okręgu jest proste:

Obwód = 2 x pi x r Obwód = 2  r

A tu mamy kod w Kotlinie, który napisaliśmy dla tych obliczeń:

val pi = 3.14
var promień = 5.2
val obwód = 2 * pi * promień

Ten kod liczy obwód okręgu o promieniu 5.2. Lecz oczywiście nie wszystkie okręgi mają promień 5.2! Co, jeśli chcemy też znaleźć obwód dla okręgu o promieniu 6.7? Albo 10.0?

Okręgi różnych rozmiarów 10.0 5.2 6.7

Cóż, moglibyśmy napisać równanie wiele razy.

val pi = 3.14

var promień = 5.2
val obwódMałegoOkręgu = 2 * pi * promień

promień = 6.7
val obwódŚredniegoOkręgu = 2 * pi * promień

promień = 10.0
val obwódDużegoOkręgu = 2 * pi * promień

To z pewnością działa, ale wow — wygląda na to, że musieliśmy w kółko pisać to samo!

Musieliśmy napisać '2 * pi * promien' wiele razyvalpi =3.14varpromień =5.2valobwódMałegoOkręgu =2* pi * promieńpromień = 6.7valobwódŚredniegoOkręgu =2* pi * promieńpromień = 10.0valobwódDużegoOkręgu =2* pi * promieńto samo

Gdy mamy ten sam kawałek kodu wiele razy jak powyżej, nazywamy to duplikacją. Zazwyczaj zduplikowany kod jest zły, ponieważ:

  1. Gdy piszesz to samo wiele razy, staje się bardziej prawdopodobne, że pewnego razu możesz popełnić błąd. Na przykład, mógłbyś raz napisać 3 * pi * promień.
  2. Jeśli chcesz zmienić równanie, musisz znaleźć wszystkie miejsca, w których je podałeś i upewnić się, że zaktualizujesz wszystkie te wystąpienia.
  3. Może być trudno czytać kod zawierający wielokrotnie ten sam fragment.

Zmieńmy nasz kod tak, aby 2 * pi * promień występowało tylko raz, a następnie użyjmy tego, aby policzyć obwód dowolnego okręgu. Innymi słowy, usuńmy duplikację!

Usuwanie duplikacji dzięki funkcjom

Pomimo że napisaliśmy 2 * pi * promień trzy razy, jedyną wartością, która była inna za każdym razem, był promień. Innymi słowy, 2 nigdy się nie zmieniało, podobnie jak pi (zawsze było równe 3.14). Wartość promień była jednak inna za każdym razem: najpierw 5.2, następnie 6.7, a na końcu 10.0.

Skoro promień to jedyne co zmienia się za każdym razem, byłoby świetnie, gdybyśmy mogli przeliczyć promień na obwód. Innymi słowy, co jeśli moglibyśmy zbudować maszynę, gdzie z jednej strony wkładamy promień, a z drugiej strony wyskakuje obwód?

Maszyna z 5.2 wchodzącym jako promień i 32.565 wychodzącym z drugiej strony.promień wchodziobwód wychodziBIP!BAM!DONG!BLIP!DING!
  • Skoro promień wchodzi, możemy nazwać to wejściem.
  • A skoro obwód wychodzi z drugiej strony, możemy nazwać to wyjściem.

Nie stworzymy prawdziwej maszyny, ale zamiast tego stworzymy funkcję, która będzie robiła dokładnie to, co chcemy — damy jej promień, a w zamian dostaniemy od niej obwód!

Podstawowe informacje o funkcjach

Tworzenie funkcji

Oto jak możesz napisać prostą funkcję w Kotlinie:

fun obwod(promien: Double) = 2 * pi * promienfunobwód(promień: Double): Double =2*pi* promieńsłowokluczowenazwafunkcjiciałofunkcjizwracanytypparametr(wejście)

Wygląda, jakby było tego dużo, ale tak naprawdę jest to tylko kilka elementów, które są proste do zrozumienia.

  1. fun jest słowem kluczowym (tak jak val czy var). Mówi Kotlinowi, że piszesz funkcję.
  2. obwód jest nazwą naszej funkcji. Możemy nazwać funkcję jak tylko nam się podoba, ale tu wybrałem obwód.
  3. (promień: Double) mówi, że funkcja ma wejście o nazwie promień, które ma typ o nazwie Double. promień nazywamy parametrem tej funkcji.
  4. : Double po nawiasie zamykającym oznacza, że wyjście z funkcji będzie typu Double. Nazywa się to typem zwracanym funkcji.
  5. 2 * pi * promień nazywa się ciałem funkcji. Jakakolwiek będzie wartość tego wyrażenia, będzie to wyjściem funkcji. Wartość tego wyjścia jest nazywana rezultatem funkcji. To jest to coś, co wychodzi z naszej maszyny. Zauważ, że jakąkolwiek wartość dostaniemy w wyniku policzenia tego wyrażenia, musi być tego samego typu podanego jako typ zwracany. W tym przykładzie, 2 * pi * promień musi zwrócić Double, albo dostaniemy błąd.

Porównajmy naszą funkcję z maszyną, którą wyobraziliśmy sobie wcześniej:

Ta sama maszyna co wcześniej, ze strzałkami wskazującymi na różne elementy funkcji w Kotlinie.promień wchodziobwód wychodzifunobwód(promień: Double): Double =2 * pi * promieńnazwa wejścianazwa tej maszynyobliczenia, któreodbywają się wtej maszynie

Możesz pamiętać z poprzedniego rozdziału, że często nie trzeba określać typu zmiennej, a zamiast tego pozwolić Kotlinowi użyć inferencji typów. Możesz też użyć inferencji typów przy pisaniu funkcji takich jak ta. Po prostu omiń typ zwracany, tak aby funkcja wyglądała tak:

fun obwód(promień: Double) = 2 * pi * promień

Programiści Kotlina często używają inferencji typów przy prostych funkcjach, takich jak ta.

Teraz gdy stworzyliśmy naszą pierwszą funkcję, czas aby ją użyć!

Wołanie funkcji

Kiedy używasz funkcji — tzn. gdy wkładasz coś do maszyny — nazywa się to wołaniem funkcji. Miejsce, w którym jest wołana, nazywa się miejscem wywołania.

Oto jak możesz wywołać funkcję w Kotlinie:

val obwodMalegoOkregu = obwod(5.2)valobwódMałegoOkręgu =obwód(5.2)zmienna (zostanie tamzapisany rezultat)nazwafunkcjiargument
  1. obwódMałegoOkręgu to zmienna, która otrzyma wartość rezultatu zawołanej funkcji (czyli tego, co wyjdzie z maszyny).
  2. obwód to nazwa funkcji, którą wołamy.
  3. Wartość 5.2 to argument tej funkcji — to, co wkładamy do maszyny. Gdy wołamy funkcję z argumentem, mówimy czasem, że przekazujemy argument do funkcji.

Od tego momentu będę zazwyczaj umieszczał nawiasy po nazwie funkcji. Na przykład, będę pisał obwód() zamiast tylko obwód. Pomoże mi to podkreślić, że mam na myśli funkcję.

Co się dzieje, gdy wołasz funkcję? Przypuśćmy, że napisaliśmy funkcję i zawołaliśmy ją, o tak:

fun obwód(promień: Double) = 2 * pi * promień

val obwódMałegoOkręgu = obwód(5.2)

Gdy wołamy tę funkcję, to trochę tak jak byśmy podmienili jej ciało - 2 * pi * promień - tam, gdzie widzimy zawołanie funkcji - obwód(5.2).

Możesz więc wyobrazić sobie to tak:

fun obwód(promień: Double) = 2 * pi * promień

val obwódMałegoOkręgu = 2 * pi * promień

Idąc dalej, skoro przekazaliśmy 5.2 jako promień, moglibyśmy wyobrazić sobie podmianę promień na wartość 5.2:

fun obwód(promień: Double) = 2 * pi * promień

val obwódMałegoOkręgu = 2 * pi * 5.2

Podsumowując, gdy wołamy obwód(5.2), to tak jak byśmy napisali w tym miejscu 2 * pi * 5.2.

Teraz gdy mamy funkcję liczącą obwód dla danego promienia, możemy zawołać ją tyle razy, ile potrzebujemy!

val pi = 3.14
fun obwód(promień: Double) = 2 * pi * promień

val obwódMałegoOkręgu = obwód(5.2)
val obwódŚredniegoOkręgu = obwód(6.7)
val obwódDużegoOkręgu = obwód(10.0)

Czy nie wygląda to ładniej niż na listingu 2.2? Dzięki temu, że mamy funkcję, nie musimy pisać w kółko 2 * pi * promień! Zamiast tego, wołamy funkcję obwód() dla każdego okręgu.

Argumenty i parametry: czym się różnią?

Łatwo jest pomylić argument z parametrem, więc ważne jest, aby zapamiętać tę różnicę:

  1. Argument to wartość, jaką przekazujemy do funkcji. Na przykład, przekazujesz 5.2 do funkcji obwód(). Argument to 5.2.
  2. Parameter to zmienna wewnątrz funkcji, która dostanie tę wartość. Na przykład, gdy przekazujesz 5.2 do funkcji obwód(), ta wartość jest przypisana do parametru o nazwie promień.

By łatwiej zapamiętać różnicę, spójrz na ten krótki artykuł.

Funkcje z więcej niż jednym parametrem

Funkcja obwód() ma tylko jeden parametr o nazwie promień, ale zdarza się, że czasami możesz potrzebować funkcji, która ma ich więcej. Stwórzmy funkcję, która ma dwa parametry!

Nawet jeśli Twoje ostatnie zajęcia z fizyki były jakiś czas temu, zapewne wiesz, jak policzyć prędkość. Łatwo to zapamiętać, bo mówimy to na głos cały czas - “Limit prędkości to 100 kilometrów na godzinę.”

“Kilometry na godzinę” to dystans (“kilometry”) podzielony przez (“na”) czas (“godzinę”).

speed equals distance divided by timeprędkć =dystansczas

W Kotlinie używamy prawego ukośnika, by wykonać dzielenie. Możesz myśleć o tym, jak o ułamku, który przewrócił się w lewo:

distance sliding off of time, resulting in a forward slashprędkć =dystansczasprędkć = dystans / czasprędkć =dystansczas

Na początek napiszmy prosty kod liczący średnią prędkość samochodu, który przejechał 321.8 kilometrów w 4.15 godzin.

val dystans = 321.8
val czas = 4.15
val prędkość = dystans / czas

Przekształćmy teraz wyrażenie dystans / czas w funkcję. Aby stworzyć funkcję liczącą prędkość, potrzebujemy znać dwie wartości: dystans i czas.

W Kotlinie, gdy potrzebujesz funkcji z dwoma parametrami, po prostu oddzielasz je przecinkiem, o tak:

fun prędkość(dystans: Double, czas: Double) = dystans / czas

Gdy chcesz zawołać tę funkcję, argumenty także oddzielone są przecinkiem. Na przykład, możemy zawołać funkcję prędkość() z tymi samymi wartościami, których użyliśmy wyżej, oddzielając je przecinkiem:

val średniaPrędkość = prędkość(321.8, 4.15)

Wynik to około 77.54 kilometrów na godzinę.

Zwróć uwagę, że argumenty są tu podane w tej samej kolejności co parametry.

  • Skoro 321.8 jest pierwszym argumentem, 321.8 będzie przypisane do pierwszego parametru, którym jest dystans.
  • Skoro 4.15 jest drugim argumentem, 4.15 będzie przypisane do drugiego parametru, którym jest czas.

Innymi słowy, pozycja argumentu ma znaczenie przy wołaniu funkcji w taki sposób, dlatego też czasami nazywamy takie argumenty argumentami pozycyjnymi.

First argument is assigned to first parameter. Second argument is assigned to second parameter.funprędkość(dystans: Double, czas: Double) = dystans  / czasfunsredniaPrędkość  =prędkość(321.8,4.15)

Ale to nie jedyny sposób, by przekazywać argumenty do funkcji!

Argumenty nazwane

Zamiast polegać na pozycji argumentów, możesz użyć nazwy parametru, o tak:

val średniaPrędkość = prędkość(dystans = 321.8, czas = 4.15)

Nazywamy je argumentami nazwanymi. Zaletą nazwanych argumentów jest brak znaczenia ich kolejności. Możesz więc zawołać funkcję w taki sposób, z argumentami w odwrotnej kolejności:

val średniaPrędkość = prędkość(czas = 4.15, dystans = 321.8)

Innymi słowy, wszystkie pięć poniższych zawołań da dokładnie taki sam wynik:

val średniaPrędkość1 = prędkość(321.8, 4.15)
val średniaPrędkość2 = prędkość(dystans = 321.8, 4.15)
val średniaPrędkość3 = prędkość(321.8, czas = 4.15)
val średniaPrędkość4 = prędkość(dystans = 321.8, czas = 4.15)
val średniaPrędkość5 = prędkość(czas = 4.15, dystans = 321.8)

Argumenty domyślne

W niektórych przypadkach możesz zauważyć, że przekazujesz tę samą wartość argumentu do funkcji na okrągło. Na przykład, może liczysz jak szybko:

  1. Idzie człowiek
  2. Ktoś inny jedzie na rowerze
  3. Trzecia osoba jedzie autem i
  4. Czwarta osoba leci samolotem.
Rysunkowe sylwetki osoby chodzącej, jadącej na rowerze, samochodu i samolotu.

Każdy poruszał się przez 2.0 godziny, z wyjątkiem samolotu, któremu udało się dotrzeć do celu po jedynie 1.5 godziny. Używając funkcji prędkość(), możesz policzyć prędkości w taki sposób:

val prędkośćChodzenia = prędkość(10.2, 2.0)
val prędkośćJazdyNaRowerze = prędkość(29.6, 2.0)
val prędkośćJazdySamochodem = prędkość(225.3, 2.0)
val prędkośćLotu = prędkość(1368.747, 1.5)

Zamiast cały czas przekazywać 2.0 dla parametru czas, mógłbyś wybrać 2.0 jako argument domyślny przy definiowaniu funkcji.

Ulepszmy naszą funkcję prędkość() tak, aby parametr czas domyślnie był równy 2.0.

fun prędkość(dystans: Double, czas: Double = 2.0) = dystans / czas 

Możemy teraz ominąć argument czas kiedykolwiek jest równy 2.0, o tak:

val prędkośćChodzenia = prędkość(10.2)
val prędkośćJazdyNaRowerze = prędkość(29.6)
val prędkośćJazdySamochodem = prędkość(225.3)
val prędkośćLotu = prędkość(1368.747, 1.5)

W przypadku chodzenia, jazdy na rowerze i jazdy samochodem, pominęliśmy argument czas, więc domyślnie ma on wartość 2.0. W przypadku lotu przekazaliśmy 1.5. Wyniki dla listingu 2.16 są identyczne z listingiem 2.14.

Proste!

A co zrobić, gdy chcemy argument domyślny dla pierwszego parametru zamiast drugiego?

Gdy argument domyślny jest pierwszy

Nasz piechur, rowerzysta, kierowca samochodu i pilot wrócili do akcji. Lecz tym razem, w wyścigu! Ktokolwiek przekroczy linię oddaloną o 42.195 kilometrów, otrzymuje nagrodę.

Wszyscy skończyli wyścig, oprócz samolotu, który złapał gumę przed wystartowaniem:

val prędkośćChodzenia = prędkość(42.195, 8.27)
val prędkośćJazdyNaRowerze = prędkość(42.195, 2.85)
val prędkośćJazdySamochodem = prędkość(42.195, 0.37)
val prędkośćLotu = prędkość(0.12, 0.01)

Zamiast ustalać domyślny czas, ustalmy domyślny dystans na 42.195:

fun prędkość(dystans: Double = 42.195, czas: Double) = dystans / czas

Skoro piechur, rowerzysta i kierowca przemierzają ten sam dystans, powinniśmy być w stanie pominąć wartość pierwszego parametru, dystans. Być może chciałbyś zawołać funkcję w taki sposób:

val prędkośćChodzenia = prędkość(8.27)
val prędkośćJazdyNaRowerze = prędkość(2.85)
val prędkośćJazdySamochodem = prędkość(0.37)
val prędkośćLotu = prędkość(0.12, 0.01)
Błąd

Prowadzi to jednak do błędu: brak wartości dla parametru czas. Dlaczego tak się stało?

Jako że używamy argumentów pozycyjnych, tak naprawdę pominęliśmy argument czas zamiast dystans.

Innymi słowy, chcieliśmy przypisać 8.27 do parametru czas, w taki sposób:

Chcieliśmy przypisać 8.27 do czasu.

ale tak naprawdę przypisaliśmy 8.27 do parametru dystans ponieważ 8.27 jest pierwszym argumentem, a dystans jest pierwszym parametrem:

We actually assigned 8.27 to distance.funprędkość(dystans: Double =42.195, czas: Double) = dystans / czasvalprędkośćChodzenia =prędkość(8.27)

Aby powiedzieć Kotlinowi, że zamiast tego ustawiamy czas, musimy po prostu użyć nazwanych argumentów, w taki sposób:

val prędkośćChodzenia = prędkość(czas = 8.27)
val prędkośćJazdyNaRowerze = prędkość(czas = 2.85)
val prędkośćJazdySamochodem = prędkość(czas = 0.37)
val prędkośćLotu = prędkość(0.12, 0.01)

Teraz pierwszy parametr, dystans, będzie miał wartość domyślną 42.195 gdy zawołamy prędkość() dla chodzenia, jazdy na rowerze i jazdy samochodem.

Ciała jako wyrażenia i jako blok

Jak na razie, pisaliśmy funkcje, które liczyły wartość wyrażenia.

  • Nasza funkcja obwód() liczy wartość wyrażenia 2 * pi * promień
  • Nasza funkcja prędkość() liczy wartość wyrażenia dystans / czas

Spójrzmy na kod funkcji obwód():

val pi = 3.14

fun obwód(promień: Double) = 2 * pi * promień

Gdy piszemy funkcję w ten sposób, kiedy jej ciało to tylko jedno wyrażenie, mówimy, że funkcja ma ciało jako wyrażenie (ang. expression body).

Kotlin dostarcza nam też drugi sposób pisania funkcji. Przepiszmy funkcję obwód() używając drugiego sposobu:

val pi = 3.14

fun obwód(promień: Double): Double {
    return 2 * pi * promień
}

Gdy piszemy funkcję w ten sposób, mówimy, że funkcja ma ciało jako blok (ang. block body).

Pisanie funkcji z ciałem jako blok jest nieco bardziej skomplikowane niż takiej z ciałem jako wyrażenie, więc przyjrzyjmy się nowym elementom:

Na początku, zauważmy : Double po nawiasie. Mogliśmy użyć inferencji typów przy funkcjach z ciałem jako wyrażenie, ale przy ciele jako blok, musimy podać jawnie zwracany typ.

Następnie, spójrzmy na otwierające i zamykające nawiasy klamrowe: { i }. Wszystko pomiędzy nimi jest nazywane blokiem kodu (dlatego nazywamy tę funkcję funkcją z ciałem jako blok!).

Na koniec, zauważmy słowo return w bloku kodu. Słowo return (ang. zwróć) to słowo kluczowe, które mówi Kotlinowi, że wyrażenie następujące po nim opisuje, co powinno zostać zwrócone przez funkcję. Tak jak w przypadku funkcji z ciałem jako wyrażenie, typ wartości zwracanej przez to wyrażenie musi pokrywać się z typem zadeklarowanym jako zwracany przez funkcję!

Cokolwiek jest zwrócone przez funkcję (np. 2 * pi * promien) musi zgadzać się z typem zwracanym, podanym po prawym nawiasie (np. Double).funobwód(promień: Double): Double {return2*pi* promień}cokolwiek jest zwrócone tutaj musi zgadzać się z typem podanym tutaj

Funkcje z ciałem jako blok wymagają więcej typowania niż funkcje z ciałem jako wyrażenie, więc po co mielibyśmy ich używać?

  • Pozwalają one napisać więcej niż jedną linię kodu w funkcji.
  • Pozwalają one na pisanie instrukcji wewnątrz bloku, nie tylko wyrażeń.

Na przykład, póki co zdefiniowaliśmy pi poza funkcją. Lecz byłoby świetnie zdefiniować ją wewnątrz funkcji:

fun obwód(promień: Double): Double {
	val pi = 3.14
	return 2 * pi * promień
}

Przez takie przeniesienie pi do środka funkcji, będzie ono dostępne tylko wewnątrz funkcji. Innymi słowy, gdy spróbujesz go użyć poza funkcją, dostaniesz błąd.

fun obwód(promień: Double): Double {
	val pi = 3.14
	return 2 * pi * promień
}

val tau = 2 * pi
Błąd

W dalszej części kursu zobaczymy dużo przykładów funkcji zarówno z ciałem jako wyrażenie, jak i ciałem jako blok.

Funkcje bez wyniku

Niekiedy możesz potrzebować funkcji, która nie zwraca wyniku. Na przykład, powiedzmy, że mamy zmienną przechowującą liczbę, której wartość rośnie, nazwaną licznik. Stworzymy funkcję zwiększ(). Za każdym razem gdy ją zawołamy, zwiększy ona licznik o jeden, dzięki instrukcji licznik = licznik + 1.

Funkcje z ciałami jako wyrażenia mogą zawierać tylko jedno wyrażenie. Nie możemy użyć instrukcji w ciele jako wyrażenie. Na przykład, nie możemy zrobić tak:

var licznik = 0

fun zwiększ() = licznik = licznik + 1
Błąd

Zamiast używać ciała jako wyrażenia, lepiej jest użyć funkcji z ciałem jako blok:

var licznik = 0

fun zwiększ() {
    licznik = licznik + 1
}

Zapewne zauważyłeś, że nie podaliśmy typu zwracanego w tej funkcji.

Nie ma podanego typu zwracanego przez tę funkcję: fun zwieksz() { licznik = licznik + 1 }funzwiększ() {licznik=licznik+1}brak zwracanego typu

Może Cię to zaskoczyć, ale pomimo braku podania typu zwracanego i braku jakiegokolwiek wyrażenia w tej funkcji, funkcja ta nadal zwraca wartość!

Właśnie tak! Gdy pomijasz typ zwracany przez funkcję o ciele jako blok, automatycznie zwraca ona specjalny typ w Kotlinie zwany Unit (ang. jednostka).

Unit - to nie jest liczba ani ciąg znaków, i niewiele da się z nim zrobić. Ale jest on pomocny w niektórych przypadkach, którym przyjrzymy się, gdy dojdziemy do typów parametryzowanych w przyszłym rozdziale.

Na razie, podsumujmy ten rozdział!

Podsumowanie

W tym rozdziale poznaliśmy:

W następnym Rozdziale 3 poznamy instrukcje warunkowe w Kotlinie, tak aby nasz kod mógł zachowywać się różnie, w zależności od sytuacji. Do zobaczenia!

Podziękowania dla Louisa CAD, Jamesa Lorenzena, Matta McKenna i Charlesa Muchene’a za recenzję tego artykułu.

Share this article:

  • Share on Twitter
  • Share on Facebook
  • Share on Reddit