11.9. Przekształcanie i składanie funkcji

Funkcje mogą być przekształcane na inne funkcje, a także składane z kilku funkcji. Na początek zajmijmy się metodami tworzącymi funkcje jednoparametrowe na podstawie funkcji z wieloma parametrami. Metoda curried jest dostępna dla funkcji mających dwa parametry lub więcej i tworzy funkcję w postaci rozwiniętej. Rozpocznijmy od przykładu użycia tej metody na funkcji dwuparametrowej.

scala> val f = (n:Int, str:String) => str * n
f: (Int, String) => String = <function2>

scala> val fc = f.curried
fc: Int => (String => String) = <function1>

Otrzymaliśmy w wyniku funkcję, która ma jeden parametr, odpowiadający pierwszemu parametrowi oryginalnej funkcji, i która zwraca inną funkcję. Ta druga funkcja również ma jeden parametr, który odpowiada drugiemu parametrowi oryginalnej funkcji. Dopiero ta druga funkcja zwraca rezultat typu String, odpowiadający rezultatowi oryginalnej funkcji. W następujący sposób można wywołać funkcje f i fc.

scala> f(2,"a")
res0: String = aa

scala> fc(2)("a")
res1: String = aa

Bezpośrednia definicja funkcji podobnej do fc mogłaby wyglądać następująco. Proszę zwrócić uwagę na to, że => wiąże z prawej strony, czyli zapis a => b => c jest równoważny zapisowi a => (b => c).

scala> val fc2 = (n:Int) => (str:String) => str * n
fc2: Int => (String => String) = <function1>

scala> fc2(2)("a")
res2: String = aa

W przypadku funkcji posiadającej więcej parametrów jej rozwijanie wygląda analogicznie. Oto przykład funkcji czteroparametrowej.

scala> val g: (String,String,String,Int) => String =
     |   (greeting,first,second,n) => greeting+" "+first+" "+second+" "+("!"*n)
g: (String, String, String, Int) => String = <function4>

scala> val gc = g.curried
gc: String => (String => (String => (Int => String))) = <function1>

scala> g("Hi","John","Green",4)
res3: String = Hi John Green !!!!

scala> gc("Hi")("John")("Green")(4)
res4: String = Hi John Green !!!!

Inną metodą przekształcającą funkcje jest metoda tupled, która również jest dostępna dla funkcji mających dwa parametry lub większą ich liczbę. Ta metoda tworzy funkcję, która ma jeden parametr będący krotką o takiej liczbie elementów, co liczba parametrów oryginalnej funkcji. Oto przykład użycia tej metody na funkcji f.

scala> val ft = f.tupled
ft: ((Int, String)) => String = <function1>

W następujący sposób można wywołać funkcję ft.

scala> val args: (Int, String) = (2,"a")
args: (Int, String) = (2,a)

scala> ft(args)
res5: String = aa

Bezpośrednia definicja podobnej funkcji mogłaby wyglądać następująco:

scala> val ft2: Function1[(Int,String),String] = (t:(Int,String)) => t._2 * t._1
ft2: ((Int, String)) => String = <function1>

scala> ft2(args)
res6: String = aa

Oto przykład dla funkcji czteroparametrowej. Proszę zwrócić uwagę na podwójne nawiasy obecne wywołaniu funkcji gt. Nawiasy zewnętrzne dotyczą listy parametrów funkcji, a wewnętrzne są ogranicznikami krotki.

scala> val gt = g.tupled
gt: ((String, String, String, Int)) => String = <function1>

scala> gt(("Hi","John","Green",4))
res7: String = Hi John Green !!!!

Metody uncurried z obiektu Function wykonują operacje odwrotne do tego, co robią metody curried. Oto przykłady:

scala> val f2 = Function.uncurried(fc)
f2: (Int, String) => String = <function2>

scala> f2(2,"a")
res8: String = aa

scala> val g2 = Function.uncurried(gc)
g2: (String, String, String, Int) => String = <function4>

scala> g2("Hi","John","Green",4)
res9: String = Hi John Green !!!!

Z kolei metody untupled z obiektu Function wykonują operacje odwrotne do tego, co robią metody tupled. W ostatnim z poniższych wyrażeń nawiasy są pojedyncze i otaczają cztery oddzielne argumenty funkcji.

scala> val f3 = Function.untupled(ft)
f3: (Int, String) => String = <function2>

scala> f3(2,"a")
res10: String = aa

scala> val g3 = Function.untupled(gt)
g3: (String, String, String, Int) => String = <function4>

scala> g3("Hi","John","Green",4)
res11: String = Hi John Green !!!!

Funkcje jednoparametrowe można składać. Złożenie h dwóch funkcji m i n definiuje się następująco: h(x) = m(n(x)). W języku Scala taka operacja jest zaimplementowana za pomocą metody compose.

scala> val m: String => String = _ * 2
m: String => String = <function1>

scala> val n: String => String = _ + "!"
n: String => String = <function1>

scala> m("a")
res12: String = aa

scala> n("a")
res13: String = a!

scala> val h = m compose n
h: String => String = <function1>

scala> h("a")
res14: String = a!a!
scala> m(n("a"))
res15: String = a!a!

Istnieje również metoda andThen, która składa funkcje w odwrotnej kolejności.

scala> val k = m andThen n
k: String => String = <function1>

scala> k("a")
res16: String = aa!

scala> n(m("a"))
res17: String = aa!

W obiekcie Function zdefiniowana jest metoda chain, która tworzy funkcję złożoną z przekazanej jej sekwencji funkcji. Spójrzmy na przykład jej użycia.

scala> val o: String => String = "[" + _ + "]"
o: String => String = <function1>

scala> val functions = List(m,n,o)
functions: List[String => String] = List(<function1>, <function1>, <function1>)

scala> val f3 = Function.chain(functions)
f3: String => String = <function1>

scala> f3("A")
res18: String = [AA!]

Taki sam rezultat można uzyskać następująco.

scala> val g3 = m andThen n andThen o
g3: String => String = <function1>

scala> g3("A")
res19: String = [AA!]

Na koniec pokażmy jeszcze przykład wykorzystujący funkcję fc z początku tego podrozdziału.

scala> (fc(2) andThen o andThen fc(4))("A")
res20: String = [AA][AA][AA][AA]

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.