Język Scala posiada możliwość zdefiniowania samodzielnych funkcji, tak jak to ma miejsce w C++, a niemożliwe jest w Javie, gdzie musimy posiłkować się metodami statycznymi. Jest wiele sposobów na zdefiniowanie funkcji lub wartości funkcjopodobnych. Poniższe przykłady zaczerpnięte zostały zaczerpnięte ze strony Matta Mighta. Przyjrzyjmy się im.
Funkcja podstawowa
Do jej deklaracji używa się słowa kluczowego def
. Za znakiem '=
' może znajdować jednoliniowa lub, ujęta w nawiasy klamrowe {}
, wieloliniowa definicja. Jawne użycie słowa kluczowego return
nie jest konieczne, bo zwracana jest zawsze wartość ostatniego wyrażenia w funkcji. Dlatego w poniższym przykładzie funkcja id
zwraca wartość parametru x
:
def id(x : Int) : Int = x println(id(3)) // Wypisuje: // 3 def plus1(x : Int) : Int = { x + 1 } println(plus1(2)) // Wypisuje: // 3
Funkcje bezparametrowe
Funkcje bezparametrowe można wywoływać z pominięciem nawiasów okrągłych:
def three() = 1 + 2 println(three()) println(three) // Oba wywołania wypisują: // 3 // 3Dodatkowo w powyższym przykładzie widać, że nie jest potrzebne jawne deklarowanie zwracanego typu, ponieważ kompilator sam się go "domyśli" (dotyczy to również funkcji z parametrami).
Funckja anonimowa
val anonId = (x : Int) => x println(anonId(3)) // Wypisuje: // 3W powyższym przykładzie wartość
anonId
posiada wywiedziony (ang. implicit) typ funkcyjny (Int) => Int = <function1>
.
Metoda apply
Obiekty lub klasy z metodą apply
mogą udawać funkcje:
object Id { def apply(x : Int) = x } // f(x) => f.apply(x) println(Id.apply(3)) println(Id(3)) // Wypisują: // 3 // 3 class Identity { def apply(x : Int) = x } val myId = new Identity println(myId.apply(3)) println(myId(3)) // Wypisują: // 3 // 3 // anonimowa klasa z metodą apply: val myOtherId = new { def apply(x : Int) = x } println(myOtherId(3)) // Wypisuje: // 3Właściwie w specyfikacji języka Scala jest to zdefiniowane na odwrót: to funkcje są skrótem myślowym dla obiektów ze zdefiniowaną metodą
apply
. Przykładowa obiektowa definicja funkcji może
wyglądać tak:
object funId { def apply(x : Int) = x override def toString() = "Czyli, jak to już zostało stwierdzone wcześniej, w Scali wszystko jest obiektem i dotyczy to również funkcji." }
Funkcje wieloargumentowe
Sprawa jest prosta: argumenty oddziela się przecinkami - tak samo jak w Javie czy C++.
def h(x : Int, y : Int) : Int = x + yWywołanie funkcji z jawnie podanymi niektórymi parametrami, a ich resztą zastąpioną znakiem '
_
' zwraca nową funkcję z pomniejszoną listą parametrów. Jej kod zmienia się w stosunku do pierwowzoru o tyle, że w jej wnętrzu
parametry podane jawnie zostają zastąpione ich wartościami podanymi w wywołaniu.
val h3 = h(3, _) println(h3(4)) // Wypisuje: // 7
Ale Scala ma jeszcze w zanadrzu funkcje rozwijane (ang. Curried)
def hC (x : Int) (y : Int) : Int = x + y // źle: hC 3 4 // dobrze: hC (3) (4) // źle: hC (3) // dobrze: hC (3) _ // źle: hC _ (4) // dobrze: hC (_:Int) (4) val plus3 = hC (_:Int) (3) val plus_3 = hC (3) _ println(plus3(10)) // Wypisuje: // 13Powyżej widać przykłady prawidłowego użycia takiej funkcji oraz możliwe złe wywołania.
Funkcje rozwijane mają tę cechę, że kolejne zestawy parametrów mogą być aplikowane etapami. Dzięki temu możliwe są złożone konstrukcje tworzące nowe języki domenowe (ang. Domain Specific Language, DSL)
def whileLoop(cond: => Boolean)(body: => Unit): Unit = if (cond) { body whileLoop(cond)(body) } var i = 10 whileLoop (i > 0) { println(i) i -= 1 }Funkcja
whileLoop
posiada dwa argumenty: cond
i body
. W momencie zastosowania tej funkcji
właściwe parametry nie są obliczane aż do momentu ich użycia w ciele pętli (linie 8 i 9). Dzięki temu powstaje podobna do javowej pętla while
z rekurencyją implementacją.
Procedury
Procedura to funkcja zwracjąca domyślnie wartość typu Unit
, który jest odpowiednikiem void
w Javie i C++:
def proc(a : Int) { println("Jestem procedurą!") } proc(10) // Wypisuje: // Jestem procedurą!Warto zapamiętać, że skrótowym zapisem wartości typu
Unit
jest '()
'.
Funkcja bezargumentowa
def argless : Unit = println("zawołano bezargumentową") argless argless // wypisuje: // zawołano bezargumentową // zawołano bezargumentową
Leniwie inicjowane pola
Pola typulazy
są bezargumentowymi funkcjami, które agregują swój wynik:
class LazyClass { lazy val x = { println("Obliczam x") ; 3 } } val lc = new LazyClass println(lc.x) println(lc.x) println(lc.x) // Wypisuje: // Obliczam x // 3 // 3 // 3Oznacza to, że tylko pierwsze odwołanie się do pola
x
powoduje wywołanie bloku inicjującego. Pola typu lazy
mają opóźnioną inicjalizację do momentu pierwszego odwołania się do takiego pola i to jest podstawowym zastosowaniem tego słowa kluczowego. Używanie tego jako funkcji jest używaniem efektów ubocznych i nie jest zalecane.
Brak komentarzy:
Prześlij komentarz