Qual è la funzione di applicazione in Scala?


311

Non l'ho mai capito dai sostantivi unmarshalling e verbing (una AddTwoclasse ne ha uno applyche ne aggiunge due!).

Capisco che è zucchero sintattico, quindi (ho dedotto dal contesto) deve essere stato progettato per rendere un po 'di codice più intuitivo.

Che significato applydà una classe con una funzione? A cosa serve e a quali scopi migliora il codice (smascheramento, nomi verbali ecc.)?

come aiuta se usato in un oggetto compagno?


4
Anche una rapida ricerca su google porta molti articoli carini. Eccone uno: jackcoughonsoftware.blogspot.com/2009/01/…
— om-nom-nom


6
In realtà è lo stesso di un costruttore in Java / C ++, ma può restituire il valore e ha 'applicare' nome
— SES

È un metodo, non una funzione. La distinzione tra metodi e funzioni è molto importante in Scala.
— destra

1
Per approfondire Zoidberg, "metodi sono solo funzioni che possono accedere allo stato della classe" twitter.github.io/scala_school/basics.html In generale, questo vale per più di Scala stackoverflow.com/questions/155609/...
— DharmaTurtle

Risposte:


641

I matematici hanno i loro piccoli modi divertenti, quindi invece di dire "allora chiamiamo funzione fpassandola xcome parametro", come direbbero i programmatori, parlano di "applicare la funzione fal suo argomento x".

In matematica e informatica, Apply è una funzione che applica funzioni agli argomenti.
Wikipedia

applyserve allo scopo di colmare il divario tra paradigmi orientati agli oggetti e funzionali in Scala. Ogni funzione in Scala può essere rappresentata come un oggetto. Ogni funzione ha anche un tipo OO: ad esempio, una funzione che accetta un Intparametro e restituisce un Inttipo OO avrà 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

Dato che tutto è un oggetto in Scala fora può essere trattato come un riferimento Function1[Int,Int]all'oggetto. Ad esempio, possiamo chiamare il toStringmetodo ereditato da Any, che sarebbe stato impossibile per una funzione pura, perché le funzioni non hanno metodi:

  f.toString

Oppure potremmo definire un altro Function1[Int,Int]oggetto chiamando il composemetodo fe concatenando due diverse funzioni insieme:

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

Ora se vogliamo effettivamente eseguire la funzione, o come dice il matematico "applica una funzione ai suoi argomenti", chiameremmo il applymetodo Function1[Int,Int]sull'oggetto:

 f2.apply(2)

Scrivere f.apply(args)ogni volta che si desidera eseguire una funzione rappresentata come oggetto è orientato agli oggetti, ma aggiungerebbe un sacco di disordine al codice senza aggiungere molte informazioni aggiuntive e sarebbe bello poter usare più notazioni standard, come come f(args). È qui che interviene il compilatore Scala e ogni volta che abbiamo un riferimento fa un oggetto funzione e scriviamo f (args)per applicare argomenti alla funzione rappresentata, il compilatore si espande silenziosamente f (args)alla chiamata del metodo oggetto f.apply (args).

Ogni funzione in Scala può essere trattata come un oggetto e funziona anche nell'altro modo - ogni oggetto può essere trattato come una funzione, purché abbia il applymetodo. Tali oggetti possono essere utilizzati nella notazione della funzione:

// 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 

Ci sono molti casi d'uso in cui vorremmo trattare un oggetto come una funzione. Lo scenario più comune è un modello di fabbrica . Invece di aggiungere disordine al codice utilizzando un metodo factory, possiamo applyobiettare a una serie di argomenti per creare una nuova istanza di una classe associata:

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) 

Quindi il applymetodo è solo un modo pratico per colmare il divario tra funzioni e oggetti in Scala.


1
come aggiungeresti un tipo di variabile personalizzato a un oggetto. AFAIK non è possibile. Ecco un esempio: hai questa classe class Average[YType](yZeroValue:YType). Come si passa YType dal suo oggetto poiché gli oggetti non possono accettare i parametri di tipo?
— Adrian,

I tipi in Scala di solito possono essere dedotti in base agli argomenti, ma in caso contrario è possibile fornirli tramite le parentesi quadre. quindi quando crei un'istanza di un oggetto medio potresti dire "val avg = new Average [Int] (0)"
— Angelo Genovese,

4
Le classi di casi meritano di essere menzionate qui. Le classi di case aggiungono applye unapplymetodi in modo conveniente, automatico e invisibile a un oggetto compagno della classe (tra le altre cose). Quindi la definizione di una classe con una sola riga in questo modo case class Car(make:String, model: String, year: Short, color: String)rende possibile la produzione di nuovi oggetti della classe Car semplicemente: val myCar = Car("VW","Golf",1999,"Blue"). Questa è una scorciatoia perCar.apply(...)
— Kjetil S.

1
every object can be treated as a function, provided it has the apply method- così tanto ora ha senso.
— Arj,


51

Viene dall'idea che spesso si desidera applicare qualcosa a un oggetto. L'esempio più accurato è quello delle fabbriche. Quando si dispone di una fabbrica, si desidera applicare un parametro per creare un oggetto.

I ragazzi di Scala hanno pensato che, come accade in molte situazioni, potrebbe essere bello avere una scorciatoia da chiamare apply. Pertanto, quando si danno parametri direttamente a un oggetto, viene desugared come se si passassero questi parametri alla funzione di applicazione di quell'oggetto:

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

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

Viene spesso utilizzato nell'oggetto companion, per fornire un metodo di fabbrica piacevole per una classe o un tratto, ecco un esempio:

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
  }
}

Dalla libreria scala standard, puoi vedere come scala.collection.Seqviene implementato: Seqè un tratto, quindi new Seq(1, 2)non verrà compilato ma grazie all'oggetto compagno e all'applicazione, puoi chiamare Seq(1, 2)e l'implementazione viene scelta dall'oggetto compagno.


L'equivalente di applicare potrebbe essere realizzato anche usando Implicits?
— jayunit100,

11

Ecco un piccolo esempio per coloro che vogliono esaminare rapidamente

 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")


} 

produzione

Un greeter-1 viene istanziato con il messaggio hello

Un greeter-2 viene istanziato con il mondo dei messaggi


2
Il messaggio per g2 è corretto? Sta accadendo realmente all'istanza, o in realtà dopo l'istanza (anche se è pigramente istanziata)?
— Tim Barrass,
Utilizzando il nostro sito, riconosci di aver letto e compreso le nostre Informativa sui cookie e Informativa sulla privacy.
Licensed under cc by-sa 3.0 with attribution required.