2012-05-25 9 views
6

Immaginate questo codice:Perché la funzione parzialmente applicata rimanda l'istanza di classe in Scala?

class Foo { 
    println("in Foo") 

    def foo(a: Int) = a + 1 
} 

Ora, se noi invochiamo: otterrà creato

new Foo().foo _ 

istanza della classe Foo, come previsto:

in Foo 
res0: (Int) => Int = <function1> 

Tuttavia, se invochiamo questo :

new Foo().foo(_) 

il costruttore di Foo non verrà chiamato:

res1: (Int) => Int = <function1> 

Se poi ci dicono:

res1(7) 

che è quando Foo viene istanziato:

in Foo 
res2: Int = 8 

Perché espansione Eta contro applicazione funzione parziale fare la differenza nell'istanza della classe?

+0

Questa domanda ha avuto una risposta, ma non riesco a vedere più. Qualcuno lo ha cancellato? –

risposta

2

Boy, quello è sottile, ma per quanto posso dire è seguendo completamente lo Scala spec. Citerò dalla versione 2.9 della specifica.

Per il vostro primo esempio: come dici giustamente, si sta vedendo l'espansione eta attraverso un caso particolare di un valore Method (§6.7):

The expression e _ is well-formed if e is of method type or if e is a call-by-name parameter. If e is a method with parameters, e _ represents e converted to a function type by eta expansion.

L'algoritmo per l'espansione eta è dato nel § 6.26.5 che è possibile seguire per dare la seguente sostituzione per l'espressione new Foo().x1 _:

{ 
    val x1 = new Foo(); 
    (y1: Int) => x1.(y1); 
} 

Ciò implica che, quando è in uso l'espansione eta, tutti i sotto-espressioni sono valutate nel punto in cui la c onversion ha luogo (se ho capito correttamente il significato della frase "massima espressione secondaria") e l'espressione finale è la creazione di una funzione anonima.

Nel vostro secondo esempio, quelle parentesi in più significa che il compilatore esaminerà §6.23 (in particolare, "Segnaposto Sintassi per funzioni anonime) e creare direttamente una funzione anonima.

An expression (of syntactic category Expr) may contain embedded underscore symbols _ at places where identifiers are legal. Such an expression represents an anonymous function where subsequent occurrences of underscores denote successive parameters.

In tal caso , e in seguito l'algoritmo in tale sezione, l'espressione finisce per essere questa:

(x1: Int) => new Foo().foo(x1) 

la differenza è sottile e, come spiegato molto bene da @Antoras, mostra solo effettivamente in presenza del codice di effetto collaterale.

Si noti che è in esecuzione un bugfix per il caso che coinvolge blocchi di codice call-by-name (vedere, ad esempio, this question, this bug e this bug).

Postscript: In entrambi i casi, la funzione anonima (x1:Int) => toto viene ampliato per

new scala.Function1[Int, Int] { 
    def apply(x1: Int): Int = toto 
} 
1

Perché si espande per

(x: Int) => new Foo().foo(x) 

Quindi, si sta creando solo quella istanza di Foo quando si chiama quella funzione.

E la ragione per cui il primo un'istanza Foo subito è perché si espande a

private[this] val c: (Int) => Int = { 
    <synthetic> val eta$0$1: Foo = new Foo(); 
    ((a: Int) => eta$0$1.foo(a)) 
}; 
<stable> <accessor> def c: (Int) => Int = Foo.this.c; 

E Foo sta ottenendo un'istanza qui una volta c è definito.

2

Io non sono del tutto sicuro, ma penso che il motivo per cui v'è una differenza, è che la Scala non è un linguaggio di programmazione puramente funzionale - permette effetti collaterali:

scala> class Adder { var i = 0; def foo(a:Int)={i+=1;println(i);a+1} } 
defined class Adder 

scala> val curriedFunction = new Adder().foo _ 
curriedFunction: (Int) => Int = <function1> 

scala> val anonymousFunction = new Adder().foo(_) 
anonymousFunction: (Int) => Int = <function1>  

scala> curriedFunction(5) 
1 
res11: Int = 6 

scala> curriedFunction(5) 
2 
res12: Int = 6 

scala> anonymousFunction(5) 
1 
res13: Int = 6 

scala> anonymousFunction(5) 
1 
res14: Int = 6 

viene trattata la funzione anonima come:

val anonymousFunction = x => new Adder().foo(x) 

considerando la funzione curry è trattata come:

val curriedFunction = { 
    val context = new Adder() 
    (a:Int) => context foo a 
} 

La funzione al curry è conforme al modo tradizionale in cui le funzioni vengono gestite in linguaggi funzionali: una funzione al curry è una funzione che viene applicata ad alcuni dati e valuta questa funzione parzialmente applicata. In altre parole: sulla base di alcuni dati viene creato un contesto che viene memorizzato e può essere utilizzato in seguito. Questo è esattamente ciò che sta facendo curriedFunction. Poiché Scala consente lo stato mutabile, il contesto può essere modificato, un fatto che può portare a comportamenti imprevisti come nella domanda.

I linguaggi puramente funzionali come Haskell non presentano questo problema perché non consentono tali effetti collaterali. In Scala bisogna assicurarsi da sé che il contesto creato dalla funzione al curry sia davvero puro.Se questo non è il caso e il comportamento delle funzioni puramente curry è richiesto, le funzioni anonime devono essere utilizzate perché non memorizzano un contesto (che può essere problematico se la creazione del contesto è costosa e deve essere fatta spesso).

Problemi correlati