Differenza tra metodo e funzione in Scala


254

Ho letto le funzioni Scala (parte di Another tour of Scala ). In quel post ha dichiarato:

I metodi e le funzioni non sono la stessa cosa

Ma non ha spiegato nulla al riguardo. Cosa stava cercando di dire?



3
Penso che tu possa ottenere qualcosa da Qual è la differenza tra un metodo e una funzione
jinglining il

Una domanda di follow-up con buone risposte: funzioni vs metodi in Scala
Josiah Yoder

Risposte:


238

Jim lo ha praticamente trattato nel suo post sul blog , ma sto postando un briefing qui come riferimento.

Innanzitutto, vediamo cosa ci dicono le specifiche della Scala. Il capitolo 3 (tipi) ci parla dei tipi di funzione (3.2.9) e dei metodi (3.3.1). Il capitolo 4 (dichiarazioni di base) parla di Dichiarazione e definizioni di valore (4.1), Dichiarazione e definizioni di variabili (4.2) e Dichiarazioni e definizioni di funzioni (4.6). Il capitolo 6 (espressioni) parla delle funzioni anonime (6.23) e dei valori dei metodi (6.7). Curiosamente, i valori delle funzioni sono parlati una volta su 3.2.9, e non altrove.

Un tipo di funzione è (approssimativamente) un tipo di modulo (T1, ..., Tn) => U , che è una scorciatoia per il tratto FunctionNnella libreria standard. Le funzioni anonime e i valori dei metodi hanno tipi di funzioni e i tipi di funzione possono essere utilizzati come parte delle dichiarazioni e definizioni di valori, variabili e funzioni. In effetti, può far parte di un tipo di metodo.

Un tipo di metodo è un tipo senza valore . Ciò significa che non esiste alcun valore - nessun oggetto, nessuna istanza - con un tipo di metodo. Come accennato in precedenza, un valore di metodo ha effettivamente un tipo di funzione . Un tipo di metodo è una defdichiarazione - tutto ciò che riguarda a deftranne il suo corpo.

Valore Dichiarazioni e definizioni e dichiarazioni di variabili e definizioni sono vale vardichiarazioni, tra cui sia il tipo e il valore - che può essere, rispettivamente Tipo funzione e funzioni anonime o valori Method . Si noti che, sulla JVM, questi (valori dei metodi) sono implementati con ciò che Java chiama "metodi".

Una dichiarazione di funzione è una defdichiarazione, inclusi tipo e corpo . La parte del tipo è il tipo di metodo e il corpo è un'espressione o un blocco . Questo è anche implementato su JVM con ciò che Java chiama "metodi".

Infine, una funzione anonima è un'istanza di un tipo di funzione (ovvero un'istanza del tratto FunctionN) e un valore del metodo è la stessa cosa! La distinzione è che un valore di metodo viene creato dai metodi, sia postfissando un carattere di sottolineatura ( m _è un valore di metodo corrispondente alla "dichiarazione di funzione" ( def) m), sia mediante un processo chiamato eta-espansione , che è come un cast automatico dal metodo funzionare.

Questo è ciò che dicono le specifiche, quindi lasciatemi mettere questo in primo piano: non usiamo quella terminologia! Porta a troppa confusione tra la cosiddetta "dichiarazione di funzione" , che fa parte del programma (capitolo 4 - dichiarazioni di base) e "funzione anonima" , che è un'espressione, e "tipo di funzione" , che è, bene un tipo - un tratto.

La terminologia seguente, e utilizzata da esperti programmatori Scala, apporta una modifica alla terminologia della specifica: invece di dire la dichiarazione di funzione , diciamo metodo . O anche la dichiarazione del metodo. Inoltre, notiamo che le dichiarazioni di valore e le dichiarazioni variabili sono anche metodi per scopi pratici.

Quindi, dato il cambiamento di terminologia sopra riportato, ecco una spiegazione pratica della distinzione.

Una funzione è un oggetto che comprende uno dei FunctionXtratti, ad esempio Function0, Function1, Function2, ecc Si potrebbe comprendendo PartialFunctionpure, che si estende in realtà Function1.

Vediamo la firma del tipo per uno di questi tratti:

trait Function2[-T1, -T2, +R] extends AnyRef

Questo tratto ha un metodo astratto (ha anche alcuni metodi concreti):

def apply(v1: T1, v2: T2): R

E questo ci dice tutto quello che c'è da sapere al riguardo. Una funzione ha un applymetodo che riceve N parametri di tipo T1 , T2 , ..., TN e restituisce qualcosa di tipo R. È contro-variante sui parametri che riceve e co-variante sul risultato.

Questa varianza significa che a Function1[Seq[T], String]è un sottotipo di Function1[List[T], AnyRef]. Essere un sottotipo significa che può essere usato al posto di esso. Si può facilmente vedere che se ho intenzione di chiamare f(List(1, 2, 3))e aspettarmi un AnyRefritorno, uno dei due tipi sopra funzionerebbe.

Ora, qual è la somiglianza di un metodo e una funzione? Bene, se fè una funzione ed mè un metodo locale all'ambito, entrambi possono essere chiamati in questo modo:

val o1 = f(List(1, 2, 3))
val o2 = m(List(1, 2, 3))

Queste chiamate sono in realtà diverse, perché la prima è solo uno zucchero sintattico. Scala lo espande a:

val o1 = f.apply(List(1, 2, 3))

Che, ovviamente, è una chiamata di metodo sull'oggetto f. Le funzioni hanno anche altri zuccheri sintattici a suo vantaggio: letterali di funzione (due di loro, in realtà) e (T1, T2) => Rfirme di tipo. Per esempio:

val f = (l: List[Int]) => l mkString ""
val g: (AnyVal) => String = {
  case i: Int => "Int"
  case d: Double => "Double"
  case o => "Other"
}

Un'altra somiglianza tra un metodo e una funzione è che il primo può essere facilmente convertito nel secondo:

val f = m _

Scala espanderà questo , supponendo che il mtipo sia (List[Int])AnyRefin (Scala 2.7):

val f = new AnyRef with Function1[List[Int], AnyRef] {
  def apply(x$1: List[Int]) = this.m(x$1)
}

Su Scala 2.8, in realtà utilizza una AbstractFunction1classe per ridurre le dimensioni della classe.

Si noti che non si può convertire il contrario - da una funzione a un metodo.

I metodi, tuttavia, hanno un grande vantaggio (beh, due - possono essere leggermente più veloci): possono ricevere parametri di tipo . Ad esempio, mentre fsopra può necessariamente specificare il tipo di Listricezione ( List[Int]nell'esempio), mpuò parametrizzarlo:

def m[T](l: List[T]): String = l mkString ""

Penso che questo copra praticamente tutto, ma sarò felice di integrare questo con le risposte a qualsiasi domanda che possa rimanere.


26
Questa spiegazione è molto chiara. Molto bene. Sfortunatamente sia il libro di Odersky / Venners / Spoon che le specifiche di Scala usano le parole "funzione" e "metodo" in qualche modo in modo intercambiabile. (È più probabile che dicano "funzione" in cui "metodo" sarebbe più chiaro, ma a volte succede anche nell'altro modo, ad esempio la sezione 6.7 della specifica, che tratta i metodi di conversione in funzioni, è chiamata "Valori del metodo". .) Penso che l'uso libero di queste parole abbia creato molta confusione quando le persone cercano di imparare la lingua.
Seth Tisue,

4
@Seth Lo so, lo so - PinS è stato il libro che mi ha insegnato Scala. Ho imparato meglio nel modo più duro, cioè paulp mi ha messo in chiaro.
Daniel C. Sobral,

4
Grande spiegazione! Ho una cosa da aggiungere: quando citate l'espansione del val f = mcompilatore come val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }dovreste sottolineare che l' thisinterno del applymetodo non si riferisce AnyRefall'oggetto, ma all'oggetto nel cui metodo val f = m _viene valutato (l' esterno this , per così dire ), poiché thisè tra i valori acquisiti dalla chiusura (come ad esempio, returncome indicato di seguito).
Holger Peine,

1
@ DanielC.Sobral, qual è il libro PinS che hai citato? Sono anche interessato a imparare Scala, e non ho trovato un libro con quel nome,
1313

5
@tldr Programming in Scala , di Odersky et all. È l'abbreviazione comune per questo (mi hanno detto che per qualche motivo non gli piaceva PiS! :)
Daniel C. Sobral,

67

Una grande differenza pratica tra un metodo e una funzione è ciò che returnsignifica. returnritorna sempre e solo da un metodo. Per esempio:

scala> val f = () => { return "test" }
<console>:4: error: return outside method definition
       val f = () => { return "test" }
                       ^

Il ritorno da una funzione definita in un metodo esegue un ritorno non locale:

scala> def f: String = {                 
     |    val g = () => { return "test" }
     | g()                               
     | "not this"
     | }
f: String

scala> f
res4: String = test

Considerando che il ritorno da un metodo locale ritorna solo da quel metodo.

scala> def f2: String = {         
     | def g(): String = { return "test" }
     | g()
     | "is this"
     | }
f2: String

scala> f2
res5: String = is this

9
Questo perché il ritorno viene catturato dalla chiusura.
Daniel C. Sobral,

4
Non riesco a pensare a una sola volta che vorrei "tornare" da una funzione a un ambito non locale. In effetti, posso vedere che si tratta di un grave problema di sicurezza se una funzione può solo decidere di voler tornare indietro nello stack. Sembra un po 'come longjmp, solo molto più facile sbagliarsi accidentalmente. Ho notato che scalac non mi lascia tornare dalle funzioni, però. Ciò significa che questo abominio è stato cancellato dalla lingua?
root

2
@root - che ne dici di tornare dall'interno di un for (a <- List(1, 2, 3)) { return ... }? Ciò viene portato a una chiusura.
Ben Lings,

Hmm ... Beh, questo è un caso d'uso ragionevole. Ha ancora il potenziale per portare a problemi orribili difficili da debug, ma questo lo mette in un contesto più sensato.
radice

1
Onestamente userò una sintassi diversa. deve returnrestituire un valore dalla funzione e una qualche forma di escapeo breako continueper tornare dai metodi.
Ryan The Leach,

38

funzione Una funzione può essere invocata con un elenco di argomenti per produrre un risultato. Una funzione ha un elenco di parametri, un corpo e un tipo di risultato. Le funzioni che sono membri di un oggetto classe, tratto o singleton sono chiamate metodi . Le funzioni definite all'interno di altre funzioni sono chiamate funzioni locali. Le funzioni con il tipo di risultato di Unità sono chiamate procedure. Le funzioni anonime nel codice sorgente sono chiamate letterali di funzione. In fase di esecuzione, i letterali di funzione vengono istanziati in oggetti chiamati valori di funzione.

Programmazione in Scala Seconda Edizione. Martin Odersky - Lex Spoon - Bill Venners


1
Una funzione può appartenere a una classe come def o come val / var. Solo i def sono metodi.
Josiah Yoder,

29

Supponiamo che tu abbia un elenco

scala> val x =List.range(10,20)
x: List[Int] = List(10, 11, 12, 13, 14, 15, 16, 17, 18, 19)

Definire un metodo

scala> def m1(i:Int)=i+2
m1: (i: Int)Int

Definire una funzione

scala> (i:Int)=>i+2
res0: Int => Int = <function1>

scala> x.map((x)=>x+2)
res2: List[Int] = List(12, 13, 14, 15, 16, 17, 18, 19, 20, 21)

Metodo Accettare Argomento

scala> m1(2)
res3: Int = 4

Definizione della funzione con val

scala> val p =(i:Int)=>i+2
p: Int => Int = <function1>

L'argomento da utilizzare è facoltativo

 scala> p(2)
    res4: Int = 4

scala> p
res5: Int => Int = <function1>

L'argomento al metodo è obbligatorio

scala> m1
<console>:9: error: missing arguments for method m1;
follow this method with `_' if you want to treat it as a partially applied function

Controlla il seguente tutorial che spiega il passaggio di altre differenze con esempi come altri esempi di diff con Metodo Vs Funzione, Uso della funzione come Variabili, creazione della funzione che ha restituito la funzione


13

Le funzioni non supportano i valori predefiniti dei parametri. I metodi lo fanno. La conversione da un metodo a una funzione perde le impostazioni predefinite dei parametri. (Scala 2.8.1)


5
C'è motivo per questo?
corazza,

7

C'è un bell'articolo qui da cui sono tratte la maggior parte delle mie descrizioni. Solo un breve confronto di funzioni e metodi per quanto riguarda la mia comprensione. Spero che sia d'aiuto:

Funzioni : sono fondamentalmente un oggetto. Più precisamente, le funzioni sono oggetti con un metodo apply; Pertanto, sono un po 'più lenti dei metodi a causa del loro sovraccarico. È simile ai metodi statici nel senso che sono indipendenti da un oggetto da invocare. Un semplice esempio di una funzione è proprio come qui sotto:

val f1 = (x: Int) => x + x
f1(2)  // 4

La riga sopra non è altro che l'assegnazione di un oggetto a un altro come object1 = object2. In realtà l'oggetto2 nel nostro esempio è una funzione anonima e il lato sinistro ottiene il tipo di oggetto a causa di ciò. Pertanto, ora f1 è un oggetto (funzione). La funzione anonima è in realtà un'istanza di Function1 [Int, Int] che significa una funzione con 1 parametro di tipo Int e valore restituito di tipo Int. Chiamare f1 senza argomenti ci darà la firma della funzione anonima (Int => Int =)

Metodi : non sono oggetti ma assegnati a un'istanza di una classe, ovvero a un oggetto. Esattamente lo stesso metodo di java o funzioni membro in c ++ (come ha sottolineato Raffi Khatchadourian in un commento a questa domanda ) e così via. Un semplice esempio di metodo è proprio come qui sotto:

def m1(x: Int) = x + x
m1(2)  // 4

La riga sopra non è una semplice assegnazione di valore ma una definizione di un metodo. Quando invochi questo metodo con il valore 2 come la seconda riga, la x viene sostituita con 2 e il risultato verrà calcolato e otterrai 4 come output. Qui otterrai un errore se scrivi semplicemente m1 perché è un metodo e necessita del valore di input. Usando _ puoi assegnare un metodo a una funzione come muggito:

val f2 = m1 _  // Int => Int = <function1>

Cosa significa "assegnare un metodo a una funzione"? Significa solo che ora hai un oggetto che si comporta allo stesso modo del metodo?
K. M,

@KM: val f2 = m1 _ è equivalente a val f2 = new Function1 [Int, Int] {def m1 (x: Int) = x + x};
Sasuke

3

Ecco un ottimo post di Rob Norris che spiega la differenza, ecco un TL; DR

I metodi in Scala non sono valori, ma le funzioni lo sono. Puoi costruire una funzione che delega a un metodo tramite η-espansione (innescata dalla cosa di sottolineatura finale).

con la seguente definizione:

un metodo è qualcosa definito con def e un valore è qualcosa che puoi assegnare a un val

In breve ( estratto dal blog ):

Quando definiamo un metodo vediamo che non possiamo assegnarlo a val.

scala> def add1(n: Int): Int = n + 1
add1: (n: Int)Int

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

Nota anche il tipo di add1, che non sembra normale; non puoi dichiarare una variabile di tipo (n: Int)Int. I metodi non sono valori.

Tuttavia, aggiungendo l'operatore postfix di espansione η (η è pronunciato "eta"), possiamo trasformare il metodo in un valore di funzione. Nota il tipo di f.

scala> val f = add1 _
f: Int => Int = <function1>

scala> f(3)
res0: Int = 4

L'effetto di _è eseguire l'equivalente di quanto segue: costruiamo Function1un'istanza che delega al nostro metodo.

scala> val g = new Function1[Int, Int] { def apply(n: Int): Int = add1(n) }
g: Int => Int = <function1>

scala> g(3)
res18: Int = 4

1

In Scala 2.13, a differenza delle funzioni, i metodi possono prendere / tornare

  • parametri di tipo (metodi polimorfici)
  • parametri impliciti
  • tipi dipendenti

Tuttavia, queste restrizioni sono aumentate in dotty (Scala 3) dai tipi di funzione polimorfica # 4672 , ad esempio la versione dotty 0.23.0-RC1 consente la seguente sintassi

Digitare i parametri

def fmet[T](x: List[T]) = x.map(e => (e, e))
val ffun = [T] => (x: List[T]) => x.map(e => (e, e))

Parametri impliciti ( parametri di contesto )

def gmet[T](implicit num: Numeric[T]): T = num.zero
val gfun: [T] => Numeric[T] ?=> T = [T] => (using num: Numeric[T]) => num.zero

Tipi dipendenti

class A { class B }
def hmet(a: A): a.B = new a.B
val hfun: (a: A) => a.B = hmet

Per altri esempi, vedere tests / run / polymorphic-Functions.scala


0

In pratica, un programmatore Scala deve solo conoscere le seguenti tre regole per usare correttamente funzioni e metodi:

  • Metodi definiti da defe letterali di funzione definiti da=> sono funzioni. È definito a pagina 143, capitolo 8 del libro di programmazione alla Scala, 4a edizione.
  • I valori delle funzioni sono oggetti che possono essere passati come qualsiasi valore. I letterali di funzione e le funzioni parzialmente applicate sono valori di funzione.
  • È possibile lasciare il carattere di sottolineatura di una funzione parzialmente applicata se è richiesto un valore di funzione in un punto del codice. Per esempio:someNumber.foreach(println)

Dopo quattro edizioni di Programmazione in Scala, è ancora un problema per le persone differenziare i due concetti importanti: funzione e valore della funzione perché tutte le edizioni non danno una spiegazione chiara. Le specifiche del linguaggio sono troppo complicate. Ho trovato che le regole di cui sopra sono semplici e accurate.

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.