11.4. Przekształcanie metod w funkcje

Metody nie są wartościami i nie są obiektami. Nie można ich wobec tego przypisać do zmiennej lub przekazać jako argumentu innej metodzie. Z kolei funkcje są wartościami i można z nimi takie operacje przeprowadzać. Choć metody nie są funkcjami, to można je przekształcić w funkcje. W pliku Functions6.scala zdefiniowana jest metoda greet.

Plik Functions6.scala:
def greet(greeting: String, name: String, surname: String) =
  greeting + " " + name + " " + surname + "!"

Metodę greet można przekształcić na funkcję w następujący sposób.

scala> :load Functions6.scala
Loading Functions6.scala...
greet: (greeting: String, name: String, surname: String)String

scala> val g = greet _
g: (String, String, String) => String = <function3>

scala> g("Hello","John","Brown")
res0: String = Hello John Brown!

Po nazwie metody został dodany znak _. Nazwa metody i znak pokreślenia są w powyższym wyrażeniu rozdzielone spacją. Gdyby tej spacji nie było, to cały napis greet_ zostałby potraktowany jako identyfikator.

Plik Functions7.scala:
def hello = "Hello!"

Metody bezparametrowe, takie jak metoda hello z pliku Functions7.scala, również można przekształcić na funkcje. Takie metody zostają przekształcone na funkcje z pustą listą parametrów.

scala> :load Functions7.scala
Loading Functions7.scala...
hello: String

scala> val h = hello _
h: () => String = <function0>

scala> h()
res1: String = Hello!

Metoda hello jest bezparametrowa i można ją wywołać bez konieczności podawania po jej nazwie nawiasów okrągłych. Natomiast funkcja h wymaga nawiasów okrągłych przy wywołaniu.

Metoda above5method, zdefiniowana w pliku Functions8.scala, porównuje liczbę podaną jako jej argument z wartością 5.

Plik Functions8.scala:
def above5method(x:Int) = x > 5

Ponieważ jest to metoda, to nie można jej użyć po prawej stronie znaku równości w poniższym poleceniu.

scala> :load Functions8.scala
Loading Functions8.scala...
above5method: (x: Int)Boolean

scala> val above5 = above5method
<console>:11: error: missing arguments for method above5method;
follow this method with `_' if you want to treat it as a partially applied function
       val above5 = above5method
                    ^

Można to zrobić po wcześniejszym przekształceniu jej na funkcję poprzez dodanie po nazwie metody znaku podkreślenia.

scala> val above5function = above5method _
above5function: Int => Boolean = <function1>

Poniższe przykłady pokazują, jak można użyć metody filter wraz z funkcją powstałą z metody above5method.

scala> :load Functions5.scala
Loading Functions5.scala...
filter: (f: Int => Boolean, list: List[Int])List[Int]

scala> filter(above5function, List(3,4,5,6,7))
res2: List[Int] = List(6, 7)

scala> filter(above5method _, List(3,4,5,6,7))
res3: List[Int] = List(6, 7)

scala> filter(above5method, List(3,4,5,6,7))
res4: List[Int] = List(6, 7)

Ostatni z przykładów nie jest błędem, mimo że na pierwszy rzut oka pierwszym argumentem w wywołaniu metody filter jest metoda above5method, a nie utworzona z niej funkcja. Znak podkreślenia służący do oznaczenia zamiany metody na funkcję można pominąć, jeśli odwołanie do metody następuje w kontekście, w którym wymagana jest funkcja. Ponieważ metoda filter spodziewa się funkcji w swoim pierwszym argumencie, taki zapis jest dozwolony. Nie ma w tym miejscu konieczności dodawania znaku podkreślenia.

Metoda greet2, zdefiniowana w pliku Functions9.scala, ma dwie listy parametrów.

Plik Functions9.scala:
def greet2(greeting: String)(name: String, surname: String) =
  greeting + " " + name + " " + surname + "!"

Co się stanie, kiedy zostanie przekształcona na funkcję?

scala> :load Functions9.scala
Loading Functions9.scala...
greet2: (greeting: String)(name: String, surname: String)String

scala> val g2 = greet2 _
g2: String => ((String, String) => String) = <function1>

Otrzymaliśmy funkcję pobierającą jeden parametr i zwracającą inną funkcję. Poniższe polecenie przedstawia wywołanie funkcji g2.

scala> g2("Hello")("John","Brown")
res5: String = Hello John Brown!

Funkcja zapisana w wartości g2 pobiera jeden argument — ("Hello") — i zwraca inną funkcję. Ta druga funkcja pobiera dwa argumenty — ("John","Brown") — i zwraca wartość typu String.

Zamiast wywoływać obie funkcje od razu w jednym wyrażeniu, możemy wywołać pierwszą z nich i zapamiętać ją w osobnej wartości.

scala> val welcome = g2("Welcome")
welcome: (String, String) => String = <function2>

Teraz możemy wywołać funkcję welcome przekazując jej dwa argumenty.

scala> welcome("John","Brown")
res6: String = Welcome John Brown!

Funkcja welcome powstała w dwóch krokach. Najpierw przekształciliśmy metodę greet na funkcję i zapisaliśmy w wartości g2. Następnie wywołaliśmy tę funkcję otrzymując funkcję welcome, która pobiera dwa argumenty i zwraca tekst pozdrowienia.

Możemy od razu otrzymać podobną funkcję za pomocą następującego wyrażenia.

scala> val hello = greet2("Hello")_
hello: (String, String) => String = <function2>

scala> hello("Peter","Brown")
res7: String = Hello Peter Brown!

Tym razem znak podkreślenia mógł być postawiony zaraz za zamkniętym nawiasem okrągłym, bez potrzeby umieszczania przed nim spacji, gdyż przed znakiem podkreślenia znajduje się nawias i nie zachodzi niebezpieczeństwo zinterpretowania zapisu jako identyfikatora. Powyższe wyrażenie zwraca funkcję podobną do otrzymanej wcześniej w dwóch krokach.

Specyfikacja języka Scala opisuje zamianę metod na funkcje w punkcie 6.7.

Język programowania Scala Wydanie 2. Copyright © Grzegorz Balcerek 2016

Licencja Creative Commons

Ten utwór jest dostępny na licencji Creative Commons Uznanie autorstwa-Na tych samych warunkach 4.0 Międzynarodowe.

Oracle and Java are registered trademarks of Oracle and/or its affiliates. Other names may be trademarks of their respective owners.