Pourquoi le compilateur Scala n’autorise-t-il pas les méthodes surchargées avec des arguments par défaut?

Bien qu’il puisse y avoir des cas valides où de telles surcharges de méthode pourraient devenir ambiguës, pourquoi le compilateur n’autorise-t-il pas le code qui n’est ni ambigu à la compilation ni à l’exécution?

Exemple:

// This fails: def foo(a: Ssortingng)(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: Ssortingng)(b: Ssortingng = "Foo") = a + b // This is OK: def foo(a: Ssortingng)(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: Ssortingng = "Foo") = a + b val bar = foo(42)_ // This complains obviously ... 

Y a-t-il des raisons pour lesquelles ces ressortingctions ne peuvent pas être assouplies un peu?

Surtout lors de la conversion de code Java fortement surchargé en Scala, les arguments par défaut sont très importants et il n’est pas intéressant de savoir après avoir remplacé de nombreuses méthodes Java par une méthode Scala que le compilateur / spécificateur impose des ressortingctions arbitraires.

J’aimerais citer Lukas Rytz (d’ ici ):

La raison en est que nous voulions un schéma de dénomination déterministe pour les méthodes générées qui renvoient des arguments par défaut. Si vous écrivez

def f(a: Int = 1)

le compilateur génère

def f$default$1 = 1

Si vous avez deux surcharges avec des valeurs par défaut sur la même position de paramètre, nous aurons besoin d’un schéma de dénomination différent. Mais nous voulons garder le code octet généré stable sur plusieurs exécutions de compilateur.

Une solution pour les futures versions de Scala pourrait consister à incorporer les noms de types des arguments autres que ceux par défaut (ceux au début d’une méthode, qui désambiguïsent les versions surchargées) dans le schéma de nommage, par exemple dans ce cas:

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

ce serait quelque chose comme:

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

Quelqu’un est prêt à écrire une proposition SIP ?

Il serait très difficile d’obtenir des spécifications lisibles et précises sur les interactions de la résolution de surcharge avec les arguments par défaut. Bien sûr, pour de nombreux cas individuels, comme celui présenté ici, il est facile de dire ce qui devrait se passer. Mais ce n’est pas assez. Nous aurions besoin d’une spécification qui détermine tous les cas de coin possibles. La résolution de surcharge est déjà très difficile à spécifier. Ajouter des arguments par défaut dans le mix le rendrait encore plus difficile. C’est pourquoi nous avons choisi de séparer les deux.

Je ne peux pas répondre à votre question, mais voici une solution:

 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, Ssortingng], b: Int = 42) = a match { case Left(i) => i + b case Right(s) => s + b } 

Si vous avez deux très longues listes d’arguments qui ne diffèrent que par un seul argument, cela pourrait valoir la peine …

Ce qui a fonctionné pour moi est de redéfinir (style Java) les méthodes de surcharge.

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

Cela garantit au compilateur quelle résolution vous souhaitez en fonction des parameters actuels.

L’un des scénarios possibles est

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

Le compilateur sera confus quant à savoir lequel appeler. En prévention d’autres dangers possibles, le compilateur autoriserait au plus une méthode surchargée ayant des arguments par défaut.

Juste ma conjecture 🙂

Je crois savoir qu’il peut y avoir des collisions de noms dans les classes compilées avec des valeurs d’argument par défaut. J’ai vu quelque chose dans ce sens mentionné dans plusieurs sujets.

La spécification d’argument nommée est ici: http://www.scala-lang.org/sites/default/files/sids/rytz/Mon,%202009-11-09,%2017:29/named-args.pdf

Il est dit:

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

Donc, pour le moment en tout cas, ça ne va pas marcher.

Vous pourriez faire quelque chose comme ce que vous pourriez faire en Java, par exemple:

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

Voici une généralisation de la réponse @Landei:

Ce que vous voulez vraiment:

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

Contournement

 def pretty(input: CanPretty, showFields: Boolean = false): Ssortingng = { 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): Ssortingng = "fun ..." private def prettyList(tree: List[Tree], showFields: Boolean): Ssortingng = "fun ..." private def prettyOption(tree: Option[Tree], showFields: Boolean): Ssortingng = "fun ..." 

Si vous avez appelé foo() lequel devrait-il invoquer?