Różnice między tymi trzema sposobami definiowania funkcji w Scali

92

Biorąc pod uwagę trzy sposoby wyrażenia tej samej funkcji f(a) := a + 1:

val f1 = (a:Int) => a + 1
def f2 = (a:Int) => a + 1
def f3:(Int => Int) = a => a + 1

Czym różnią się te definicje? REPL nie wskazuje żadnych oczywistych różnic:

scala> f1
res38: (Int) => Int = <function1>
scala> f2
res39: (Int) => Int = <function1>
scala> f3
res40: (Int) => Int = <function1>
qrest
źródło
11
Należy zauważyć, że w drugim bloku powyżej, ocena f1w REPL pokazuje wartość statycznie związaną f1podczas obliczania f2i f3pokazuje wynik wywołania tych metod. W szczególności nowa Function1[Int, Int]instancja jest tworzona za każdym razem , gdy jest wywoływana f2lub f3jest wywoływana, podczas gdy f1jest taka sama na Function1[Int, Int]zawsze.
Randall Schulz,
@RandallSchulz biorąc pod uwagę, że wersja val nie wymaga nowej instancji funkcji, po co w takim przypadku używać def?
virtualeyes
2
@virtualeyes Jedyną sytuacją, którą mogę sobie przypomnieć, w której widzimy, że wartości Def [...] dostarczają wartości FunctionN [...] jest w bibliotece parsera kombinatora. Nieczęsto pisze się metody, które dają funkcje i praktycznie nigdy nie używa się definicji do uzyskania wielu kopii funkcji niezmiennej semantycznie / funkcjonalnie.
Randall Schulz,

Odpowiedzi:

112

f1 jest funkcją, która przyjmuje liczbę całkowitą i zwraca liczbę całkowitą.

f2jest metodą z zerową arancją, która zwraca funkcję, która przyjmuje liczbę całkowitą i zwraca liczbę całkowitą. (Kiedy f2później wpiszesz REPL, stanie się to wywołaniem metody f2).

f3jest taki sam jak f2. Po prostu nie stosujesz tam wnioskowania o typie.

missingfaktor
źródło
6
Dlaczego f1a functioni f2jest method?
Freewind
17
@Freewind, funkcja to obiekt z metodą o nazwie apply. Metoda to metoda.
missingfaktor
Świetna odpowiedź. Pytanie: mówisz, że f2 ma zerową arancję, ale czy nie jest to jednoargumentowe? en.wikipedia.org/wiki/Arity "Funkcja pusta nie przyjmuje argumentów. Funkcja jednoargumentowa przyjmuje jeden argument." Po prostu ciekawy!
Matthew Cornell,
5
@MatthewCornell f2sam w sobie nie przyjmuje żadnych argumentów. Obiekt funkcji, który zwraca, robi.
missingfaktor
122

Wewnątrz klasy valjest oceniany podczas inicjalizacji, natomiast defjest oceniany tylko wtedy, gdy i za każdym razem wywoływana jest funkcja. W poniższym kodzie zobaczysz, że x jest oceniane przy pierwszym użyciu obiektu, ale nie ponownie, gdy uzyskuje się dostęp do elementu x. W przeciwieństwie do tego wartość y nie jest oceniana, gdy tworzona jest instancja obiektu, ale jest oceniana za każdym razem, gdy uzyskuje się dostęp do elementu członkowskiego.

  class A(a: Int) {
    val x = { println("x is set to something"); a }
    def y = { println("y is set to something"); a }
  }

  // Prints: x is set to something
  val a = new A(1)

  // Prints: "1"
  println(a.x)

  // Prints: "1"                               
  println(a.x)

  // Prints: "y is set to something" and "1"                                  
  println(a.y)

  // Prints: "y is set to something" and "1"                                                                                   
  println(a.y)
Jacek
źródło
@JacobusR czy to prawda tylko w klasie?
Andrew Cassidy
na przykład: scala> var b = 5 b: Int = 5 scala> val a: (Int => Int) = x => x + ba: Int => Int = <function1> scala> a (5) res48: Int = 10 scala> b = 6 b: Int = 6 scala> a (5) res49: Int = 11 Spodziewałem się, że a (5) zwróci 10, a wartość b zostanie wstawiona
Andrew Cassidy
@AndrewCassidy funkcja ajest niezmienna i oceniana podczas inicjalizacji, ale bpozostaje zmienną wartością. Zatem odniesienie do bjest ustawiane podczas inicjalizacji, ale wartość przechowywana przez bpozostaje zmienna. Dla zabawy możesz teraz stworzyć nowy val b = 123. Po tym a(5)zawsze będziesz dawał 11, ponieważ bjest to teraz zupełnie nowa wartość.
Jack
@JacobusR dzięki ... to ma sens. Zbiega się to z definicją „zakresu leksykalnego”, ponieważ funkcja a zawiera odniesienie do oryginalnej „var b”. Myślę, że to, co mnie wprawiło w zakłopotanie, to stwierdzenie: var b = 5; val c = b; b = 6; działa inaczej. Wydaje mi się, że nie powinienem oczekiwać, że definicja funkcji, która zawiera odniesienia do oryginalnego „leksykalnego” zakresu, będzie zachowywać się tak samo jak Int.
Andrew Cassidy
3

Wykonanie definicji takiej jak def x = e nie spowoduje oceny wyrażenia e . Zamiast tego e jest oceniane zawsze, gdy używane jest x . Alternatywnie Scala oferuje definicję wartości val x = e , która ocenia prawą stronę e jako część oceny definicji. Jeśli następnie zostanie użyte x , jest natychmiast zastępowane przez wstępnie obliczoną wartość e , dzięki czemu wyrażenie nie musi być ponownie obliczane.

Scala na przykładzie Martina Odersky'ego

Alexander
źródło