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:
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?
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!
Gdy mamy ten sam kawałek kodu wiele razy jak powyżej, nazywamy to duplikacją. Zazwyczaj zduplikowany kod jest zły, ponieważ:
- 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ń
.- 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.
- 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 jakpi
(zawsze było równe3.14
). Wartośćpromień
była jednak inna za każdym razem: najpierw5.2
, następnie6.7
, a na końcu10.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?
- 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:
Wygląda, jakby było tego dużo, ale tak naprawdę jest to tylko kilka elementów, które są proste do zrozumienia.
fun
jest słowem kluczowym (tak jakval
czyvar
). Mówi Kotlinowi, że piszesz funkcję.obwód
jest nazwą naszej funkcji. Możemy nazwać funkcję jak tylko nam się podoba, ale tu wybrałemobwód
.(promień: Double)
mówi, że funkcja ma wejście o nazwiepromień
, które ma typ o nazwieDouble
.promień
nazywamy parametrem tej funkcji.: Double
po nawiasie zamykającym oznacza, że wyjście z funkcji będzie typuDouble
. Nazywa się to typem zwracanym funkcji.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:
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:
obwódMałegoOkręgu
to zmienna, która otrzyma wartość rezultatu zawołanej funkcji (czyli tego, co wyjdzie z maszyny).obwód
to nazwa funkcji, którą wołamy.- 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 tylkoobwó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 miejscu2 * 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ę:
- Argument to wartość, jaką przekazujemy do funkcji. Na przykład, przekazujesz
5.2
do funkcjiobwód()
. Argument to5.2
.- Parameter to zmienna wewnątrz funkcji, która dostanie tę wartość. Na przykład, gdy przekazujesz
5.2
do funkcjiobwód()
, ta wartość jest przypisana do parametru o nazwiepromień
.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 nazwiepromień
, 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ę”).
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:
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
iczas
.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 jestdystans
.- Skoro
4.15
jest drugim argumentem,4.15
będzie przypisane do drugiego parametru, którym jestczas
.Innymi słowy, pozycja argumentu ma znaczenie przy wołaniu funkcji w taki sposób, dlatego też czasami nazywamy takie argumenty argumentami pozycyjnymi.
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:
- Idzie człowiek
- Ktoś inny jedzie na rowerze
- Trzecia osoba jedzie autem i
- Czwarta osoba leci samolotem.
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 parametruczas
, mógłbyś wybrać2.0
jako argument domyślny przy definiowaniu funkcji.Ulepszmy naszą funkcję
prędkość()
tak, aby parametrczas
domyślnie był równy2.0
.fun prędkość(dystans: Double, czas: Double = 2.0) = dystans / czas
Możemy teraz ominąć argument
czas
kiedykolwiek jest równy2.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śmy1.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ślnydystans
na42.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łądProwadzi 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 parametruczas
, w taki sposób:ale tak naprawdę przypisaliśmy
8.27
do parametrudystans
ponieważ8.27
jest pierwszym argumentem, adystans
jest pierwszym parametrem: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łamyprę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żenia2 * pi * promień
- Nasza funkcja
prędkość()
liczy wartość wyrażeniadystans / 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łoworeturn
(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ę!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łądW 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 onalicznik
o jeden, dzięki instrukcjilicznik = 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łądZamiast 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.
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:
- Czym jest funkcja i jak może nam pomóc usunąć powtarzalny kod.
- Czym jest parametr funkcji i czym się różni od argumentu zawołania funkcji.
- Różnicę między pozycyjnymi i nazwanymi argumentami.
- Jak ustalić wartości domyślne* argumentów.
- Różnicę między ciałem jako wyrażenie i ciałem jako blok*.
- Jak Kotlin używa typu
Unit
gdy nie mamy sensownego wynik do zwrócenia z funkcji.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.