Jaka jest funkcja zastosowania w Scali?


311

Nigdy nie zrozumiałem tego na podstawie wymyślonych rzeczowników AddTwoodznaczania i sprawdzania ( klasa ma applydwa, które dodają dwa!) Przykłady.

Rozumiem, że to cukier syntaktyczny, więc (wydedukowałem z kontekstu) musiał być zaprojektowany tak, aby niektóre kody były bardziej intuicyjne.

Jakie znaczenie ma klasa z applyfunkcją? Do czego służy i do jakich celów poprawia kod (odznaczanie, sprawdzanie rzeczowników itp.)?

jak to pomaga, gdy jest używane w obiekcie towarzyszącym?


4
Nawet szybkie googling przynosi wiele fajnych artykułów. Oto jeden: jackcoughonsoftware.blogspot.com/2009/01/…
— om-nom-nom


6
w rzeczywistości jest taki sam jak konstruktor w Javie / C ++, ale może zwrócić wartość i ma nazwę „zastosuj”
— ses

To metoda, a nie funkcja. Rozróżnienie między metodami i funkcjami jest bardzo ważne w Scali.
— prawej

1
Aby opracować Zoidberg, „Metody to tylko funkcje, które mogą uzyskać dostęp do stanu klasy” twitter.github.io/scala_school/basics.html Zasadniczo dotyczy to więcej niż Scala stackoverflow.com/questions/155609/...
— DharmaTurtle

Odpowiedzi:


640

Matematycy mają swoje własne zabawne sposoby, więc zamiast mówić „wtedy funkcję nazywamy fprzekazując ją xjako parametr”, jak powiedzieliby to programiści, mówią o „zastosowaniu funkcji fdo jej argumentu x”.

W matematyce i informatyce Apply to funkcja, która stosuje funkcje do argumentów.
Wikipedia

applysłuży do zlikwidowania luki między paradygmatami obiektowymi i funkcjonalnymi w Scali. Każda funkcja w Scali może być reprezentowana jako obiekt. Każda funkcja ma również typ OO: na przykład funkcja, która pobiera Intparametr i zwraca an, Intbędzie miała typ OO Function1[Int,Int].

 // define a function in scala
 (x:Int) => x + 1

 // assign an object representing the function to a variable
 val f = (x:Int) => x + 1

Ponieważ w Scali wszystko jest przedmiotem, fmożna je teraz traktować jako odniesienie do Function1[Int,Int]obiektu. Na przykład możemy wywołać toStringmetodę odziedziczoną po Any, która byłaby niemożliwa dla czystej funkcji, ponieważ funkcje nie mają metod:

  f.toString

Lub możemy zdefiniować inny Function1[Int,Int]obiekt, wywołując composemetodę fi łącząc dwie różne funkcje razem:

 val f2 = f.compose((x:Int) => x - 1)

Teraz, jeśli chcemy faktycznie wykonać funkcję lub, jak matematyk mówi „zastosuj funkcję do jej argumentów”, wywołalibyśmy applymetodę na Function1[Int,Int]obiekcie:

 f2.apply(2)

Pisanie za f.apply(args)każdym razem, gdy chcesz wykonać funkcję reprezentowaną jako obiekt, jest zorientowane obiektowo, ale dodałoby dużo bałaganu do kodu bez dodawania wielu dodatkowych informacji i byłoby fajnie móc użyć bardziej standardowej notacji, takiej jak jak f(args). Właśnie tam wkracza kompilator Scala, a gdy tylko mamy odwołanie fdo obiektu funkcji i piszemy, f (args)aby zastosować argumenty do reprezentowanej funkcji, kompilator po cichu rozwija się f (args)do wywołania metody obiektu f.apply (args).

Każda funkcja w Scali może być traktowana jako obiekt i działa również w drugą stronę - każdy obiekt może być traktowany jako funkcja, pod warunkiem, że ma taką applymetodę. Takie obiekty mogą być używane w notacji funkcji:

// we will be able to use this object as a function, as well as an object
object Foo {
  var y = 5
  def apply (x: Int) = x + y
}


Foo (1) // using Foo object in function notation 

Istnieje wiele przypadków użycia, w których chcielibyśmy traktować obiekt jako funkcję. Najczęstszym scenariuszem jest wzór fabryczny . Zamiast dodawać bałaganu do kodu za pomocą metody fabrycznej, możemy applysprzeciwić się zestawowi argumentów, aby utworzyć nową instancję powiązanej klasy:

List(1,2,3) // same as List.apply(1,2,3) but less clutter, functional notation

// the way the factory method invocation would have looked
// in other languages with OO notation - needless clutter
List.instanceOf(1,2,3) 

Dlatego applymetoda jest po prostu wygodnym sposobem na zlikwidowanie luki między funkcjami a obiektami w Scali.


1
jak dodasz niestandardowy typ zmiennej do obiektu. AFAIK to niemożliwe. Oto przykład: masz tę klasę class Average[YType](yZeroValue:YType). Jak przekazać YType ze swojego obiektu, skoro obiekty nie mogą przyjmować parametrów typu?
— Adrian

Typy w Scali zwykle można wywnioskować na podstawie argumentów, ale jeśli nie, możesz podać je w nawiasach kwadratowych. dlatego podczas tworzenia wystąpienia obiektu Average można powiedzieć „val avg = new Average [Int] (0)”
— Angelo Genovese

4
Warto wspomnieć o klasach przypadków. Klasy przypadków wygodnie, automagicznie i niewidocznie dodaje applyi unapplymetody do obiektu towarzyszącego klasy (między innymi). Tak definiując klasę z tylko jedną linię jak ta case class Car(make:String, model: String, year: Short, color: String)umożliwia produkcję nowych obiektów klasy samochodów po prostu: val myCar = Car("VW","Golf",1999,"Blue"). To jest skrót odCar.apply(...)
— Kjetil S.

1
every object can be treated as a function, provided it has the apply method- tyle teraz ma sens.
— Arj


51

Wynika to z faktu, że często chcesz zastosować coś do obiektu. Bardziej dokładnym przykładem jest fabryka. Gdy masz fabrykę, chcesz zastosować do niej parametr, aby utworzyć obiekt.

Scala faceci myśleli, że jak to często bywa w wielu sytuacjach, fajnie byłoby mieć skrót do połączenia apply. Zatem, gdy podajesz parametry bezpośrednio do obiektu, jest on wyłączany tak, jakbyś przekazał te parametry do funkcji zastosuj tego obiektu:

class MyAdder(x: Int) {
  def apply(y: Int) = x + y
}

val adder = new MyAdder(2)
val result = adder(4) // equivalent to x.apply(4)

Jest często używany w obiekcie towarzyszącym, aby zapewnić miłą fabryczną metodę dla klasy lub cechy, oto przykład:

trait A {
  val x: Int
  def myComplexStrategy: Int
}

object A {
  def apply(x: Int): A = new MyA(x)

  private class MyA(val x: Int) extends A {
    val myComplexStrategy = 42
  }
}

Ze standardowej biblioteki scala możesz zobaczyć, jak scala.collection.Seqjest implementowany: Seqjest cechą, dlatego new Seq(1, 2)nie będzie się kompilował, ale dzięki obiektowi towarzyszącemu i zastosowaniu możesz wywoływać, Seq(1, 2)a implementacja jest wybierana przez obiekt towarzyszący.


Czy ekwiwalent zastosowania można również osiągnąć za pomocą Implicits?
— jayunit100,

11

Oto mały przykład dla tych, którzy chcą szybko przejrzeć

 object ApplyExample01 extends App {


  class Greeter1(var message: String) {
    println("A greeter-1 is being instantiated with message " + message)


  }

  class Greeter2 {


    def apply(message: String) = {
      println("A greeter-2 is being instantiated with message " + message)
    }
  }

  val g1: Greeter1 = new Greeter1("hello")
  val g2: Greeter2 = new Greeter2()

  g2("world")


} 

wynik

Zostaje utworzona instancja Greeter-1 z komunikatem hello

Program witający-2 jest tworzony w świecie wiadomości


2
Czy komunikat dla g2 jest poprawny? Czy dzieje się tak naprawdę w momencie tworzenia instancji, czy też w rzeczywistości po utworzeniu instancji (nawet jeśli jest to leniwie utworzone)?
— Tim Barrass
Korzystając z naszej strony potwierdzasz, że przeczytałeś(-aś) i rozumiesz nasze zasady używania plików cookie i zasady ochrony prywatności.
Licensed under cc by-sa 3.0 with attribution required.