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?
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?
Risposte:
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 FunctionN
nella 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 def
dichiarazione - tutto ciò che riguarda a def
tranne il suo corpo.
Valore Dichiarazioni e definizioni e dichiarazioni di variabili e definizioni sono val
e var
dichiarazioni, 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 def
dichiarazione, 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 FunctionX
tratti, ad esempio Function0
, Function1
, Function2
, ecc Si potrebbe comprendendo PartialFunction
pure, 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 apply
metodo 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 AnyRef
ritorno, 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) => R
firme 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 m
tipo sia (List[Int])AnyRef
in (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 AbstractFunction1
classe 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 f
sopra può necessariamente specificare il tipo di List
ricezione ( List[Int]
nell'esempio), m
può 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.
val f = m
compilatore come val f = new AnyRef with Function1[List[Int], AnyRef] { def apply(x$1: List[Int]) = this.m(x$1) }
dovreste sottolineare che l' this
interno del apply
metodo non si riferisce AnyRef
all'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, return
come indicato di seguito).
Una grande differenza pratica tra un metodo e una funzione è ciò che return
significa. return
ritorna 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
for (a <- List(1, 2, 3)) { return ... }
? Ciò viene portato a una chiusura.
return
restituire un valore dalla funzione e una qualche forma di escape
o break
o continue
per tornare dai metodi.
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
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
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>
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 Function1
un'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
In Scala 2.13, a differenza delle funzioni, i metodi possono prendere / tornare
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
In pratica, un programmatore Scala deve solo conoscere le seguenti tre regole per usare correttamente funzioni e metodi:
def
e letterali di funzione definiti da=>
sono funzioni. È definito a pagina 143, capitolo 8 del libro di programmazione alla Scala, 4a edizione.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.