Perché il compilatore Scala non supporta i metodi sovraccaricati con gli argomenti predefiniti?

Mentre potrebbero esserci casi validi in cui tali sovraccarichi di metodo potrebbero diventare ambigui, perché il compilatore non accetta il codice che non è ambiguo in fase di compilazione né in fase di esecuzione?

Esempio:

// This fails: def foo(a: String)(b: Int = 42) = a + b def foo(a: Int) (b: Int = 42) = a + b // This fails, too. Even if there is no position in the argument list, // where the types are the same. def foo(a: Int) (b: Int = 42) = a + b def foo(a: String)(b: String = "Foo") = a + b // This is OK: def foo(a: String)(b: Int) = a + b def foo(a: Int) (b: Int = 42) = a + b // Even this is OK. def foo(a: Int)(b: Int) = a + b def foo(a: Int)(b: String = "Foo") = a + b val bar = foo(42)_ // This complains obviously ... 

Ci sono dei motivi per cui queste restrizioni non possono essere allentate un po ‘?

Soprattutto quando si converte codice Java pesantemente sovraccarico in argomenti predefiniti di Scala è molto importante e non è bello scoprirlo dopo aver sostituito molti metodi Java con un metodo Scala che lo spec / compilatore impone restrizioni arbitrarie.

Mi piacerebbe citare Lukas Rytz (da qui ):

Il motivo è che volevamo uno schema di denominazione deterministico per i metodi generati che restituiscono argomenti predefiniti. Se scrivi

def f(a: Int = 1)

il compilatore genera

def f$default$1 = 1

Se hai due sovraccarichi con valori predefiniti sulla stessa posizione del parametro, avremmo bisogno di uno schema di denominazione diverso. Ma vogliamo mantenere stabile il codice byte generato su più esecuzioni del compilatore.

Una soluzione per la versione futura di Scala potrebbe essere quella di incorporare i nomi di tipo degli argomenti non predefiniti (quelli all’inizio di un metodo, che disambiguano le versioni sovraccaricate) nello schema di denominazione, ad esempio in questo caso:

 def foo(a: String)(b: Int = 42) = a + b def foo(a: Int) (b: Int = 42) = a + b 

sarebbe qualcosa di simile:

 def foo$String$default$2 = 42 def foo$Int$default$2 = 42 

Qualcuno disposto a scrivere una proposta SIP ?

Sarebbe molto difficile ottenere una specifica leggibile e precisa per le interazioni di risoluzione di sovraccarico con argomenti predefiniti. Certo, per molti casi individuali, come quello presentato qui, è facile dire cosa dovrebbe accadere. Ma non è abbastanza. Avremmo bisogno di una specifica che decida tutti i casi d’angolo possibili. La risoluzione del sovraccarico è già molto difficile da specificare. Aggiungere argomenti predefiniti nel mix renderebbe ancora più difficile. Ecco perché abbiamo scelto di separare i due.

Non posso rispondere alla tua domanda, ma ecco una soluzione:

 implicit def left2Either[A,B](a:A):Either[A,B] = Left(a) implicit def right2Either[A,B](b:B):Either[A,B] = Right(b) def foo(a: Either[Int, String], b: Int = 42) = a match { case Left(i) => i + b case Right(s) => s + b } 

Se hai due elenchi di argomenti molto lunghi che differiscono in un solo argomento, potrebbe valere la pena …

Ciò che ha funzionato per me è ridefinire (in stile Java) i metodi di overloading.

 def foo(a: Int, b: Int) = a + b def foo(a: Int, b: String) = a + b def foo(a: Int) = a + "42" def foo(a: String) = a + "42" 

Ciò garantisce al compilatore quale risoluzione si desidera in base ai parametri attuali.

Uno dei possibili scenari è

 def foo(a: Int)(b: Int = 10)(c: String = "10") = a + b + c def foo(a: Int)(b: String = "10")(c: Int = 10) = a + b + c 

Il compilatore sarà confuso su quale chiamare. Nella prevenzione di altri possibili pericoli, il compilatore consentirebbe al massimo un metodo sovraccarico con argomenti predefiniti.

Solo la mia ipotesi 🙂

La mia comprensione è che ci possono essere collisioni di nomi nelle classi compilate con valori di argomento predefiniti. Ho visto qualcosa di simile menzionato in diversi thread.

La specifica argomento con nome è qui: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf

Afferma:

  Overloading If there are multiple overloaded alternatives of a method, at most one is allowed to specify default arguments. 

Quindi, per il momento, non funzionerà.

Potresti fare qualcosa di simile a ciò che potresti fare in Java, ad esempio:

 def foo(a: String)(b: Int) = a + (if (b > 0) b else 42) 

Ecco una generalizzazione della risposta @Landei:

Cosa vuoi veramente:

 def pretty(tree: Tree, showFields: Boolean = false): String = // ... def pretty(tree: List[Tree], showFields: Boolean = false): String = // ... def pretty(tree: Option[Tree], showFields: Boolean = false): String = // ... 

Workarround

 def pretty(input: CanPretty, showFields: Boolean = false): String = { input match { case TreeCanPretty(tree) => prettyTree(tree, showFields) case ListTreeCanPretty(tree) => prettyList(tree, showFields) case OptionTreeCanPretty(tree) => prettyOption(tree, showFields) } } sealed trait CanPretty case class TreeCanPretty(tree: Tree) extends CanPretty case class ListTreeCanPretty(tree: List[Tree]) extends CanPretty case class OptionTreeCanPretty(tree: Option[Tree]) extends CanPretty import scala.language.implicitConversions implicit def treeCanPretty(tree: Tree): CanPretty = TreeCanPretty(tree) implicit def listTreeCanPretty(tree: List[Tree]): CanPretty = ListTreeCanPretty(tree) implicit def optionTreeCanPretty(tree: Option[Tree]): CanPretty = OptionTreeCanPretty(tree) private def prettyTree(tree: Tree, showFields: Boolean): String = "fun ..." private def prettyList(tree: List[Tree], showFields: Boolean): String = "fun ..." private def prettyOption(tree: Option[Tree], showFields: Boolean): String = "fun ..." 

Se hai chiamato foo() quale dovrebbe essere invocato?