2011-10-07 1 views
6

Nell'esempio di codice seguente, non capisco perché la funzione fun può essere passata come argomento al metodo addAction. Il metodo fun è di tipo Unit, mentre il metodo addAction prevede una funzione di tipo () => Unit.Tipi di metodi Scala e metodi come parametri

Se fun è di tipo () => Unit, allora perché il compilatore si lamentano che fun è di tipo Unit, quando provo ad aggiungere alla lista fun azioni: actions = fun :: actions?

package myscala 

object MyScala { 

    def fun() { println("fun1 executed.") } 

    def addAction(a:() => Unit) { 
    actions = a :: actions 
    } 

    var actions: List[() => Unit] = List() 

    def main(args: Array[String]) { 
    // the following line would produce a compiler error (found: Unit, required:() => Unit), it's OK 
    // actions = fun :: actions 
    actions = (() => fun) :: actions // OK 
    // I would expect the same compiler error here (found: Unit, required:() => Unit), but it's OK why? 
    addAction(fun) 
    actions.foreach(_()) // prints twice "fun1 executed" 
    } 
} 

risposta

8

prendere questo come un esempio introduttivo:

def fun() { println("fun1 executed.") } 

val a1 = fun 
val a2:() => Unit = fun 

Entrambe le linee di compilare e (grazie a inferenza di tipo) sembrano equivalenti. Tuttavia a1 è di tipo Unit mentre a2 è di tipo () => Unit ... Com'è possibile?

Poiché non si forniscono esplicitamente tipo di a1, compilatori interpreta fun come metodo fun chiamata di tipo Unit, quindi al tipo di a1 è lo stesso tipo di fun. Significa anche che questa linea stamperà fun1 eseguita.

Tuttavia, a2 ha dichiarato esplicitamente il tipo di () => Unit. Il compilatore ti aiuta qui e capisce che dal momento che il contesto richiede una funzione di tipo () => Unit e hai fornito un metodo che corrisponde a questo tipo, non dovrebbe chiamare quel metodo, ma trattarlo come funzione di prima classe!

Non siete condannati a specificare il tipo di a1 in modo esplicito. Spiegazione:

val a1 = fun _ 

Ora capisci dove si trova il tuo problema?

+0

Sì, lo so. Questo ora mi sembra ovvio (non lo era affatto in quel momento). Quando il compilatore è in grado di dedurre che un tipo di funzione è previsto, posso semplicemente scrivere 'fun', altrimenti devo esplicitare che sto passando una funzione. Grazie per le risposte chiare a tutti voi! – Manu

+0

@Manu: considera di accettare una risposta che consideri la migliore (non necessariamente questa) –

5

è necessario scrivere fun _ nel primo caso per evitare di chiamare il metodo e l'esecuzione di eta-espansione, invece.

Ciò funzionerà:

actions = (fun _) :: actions 

Se non si esegue questa operazione, quindi fun viene valutata.

Per ulteriori dettagli, vedere la sezione 6.7 (Valori metodo) di Scala Language Reference.

Per quanto riguarda il motivo per cui fun non viene valutato nel secondo caso, è perché l'inferenza di tipo può chiaramente concludere che addAction si aspetta una funzione. A proposito, il tipo di fun è tecnicamente ()Unit, non Unit, ovvero un tipo di metodo e non un tipo di valore. Vedere la Sezione 3.3.1 nel reference per ulteriori informazioni.

+3

preferirei consigliare una lettura più chiara: [Programmazione in Scala Capitolo 9] (http://www.artima.com/pins1ed/control-abstraction.html) – Jamil

3

C'è una differenza tra metodi e funzioni. Nel tuo caso actions c'è un elenco di funzioni.Quando il compilatore sa che è necessaria una funzione (come nel caso di addAction) può convertire automaticamente un metodo fun in una funzione. Ora anche :: è un metodo, quindi il compilatore sa anche che assume funzioni come parametri. Ma il problema è lo zucchero sintattico dell'operatore associativo destro ::. Se dovessi chiamarlo come metodo: actions.::(fun) verrà compilato (anche se non posso testarlo al momento). Quando si scrive fun :: actions il compilatore pensa che fun sia un'espressione e quindi la valuta e dal momento che "restituisce" un Unit si ottiene l'errore del compilatore.

EDIT

dato che ora ho la possibilità di testare la mia ipotesi (che era sbagliato) qui sono le opzioni:

// Usual syntax 
actions.::[() => Unit](fun) 
actions.::(fun:() => Unit) 
actions.::(fun _) 
// Operator syntax 
(fun:() => Unit) :: actions 
(fun _) :: actions 
+0

'azioni.: :(divertente)' restituisce 'Elenco [Qualsiasi] = Elenco (())'. Tuttavia sta ancora valutando la funzione. Mi chiedo perché l'Elenco [() => Unità] è stato convertito in Elenco [Qualsiasi]? co-varianza? – Jamil

+2

Sì, 'List [Any]' è il tipo più preciso per un elenco che può contenere 'Unit's e'() => Unit's. L'unità ', vista come'() 'è ciò che restituisce la valutazione di' fun'. – Philippe

+0

Ho modificato la mia risposta. – agilesteel