web-dev-qa-db-fra.com

Pourquoi le compilateur Scala interdit-il 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 peuvent devenir ambiguës, pourquoi le compilateur interdit-il le code qui n'est ni ambigu au moment de la compilation ni au moment de l'exécution?

Exemple:

// 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 ...

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

Surtout lors de la conversion de surcharge Java en Scala sont très importants et il n'est pas agréable de le découvrir après avoir remplacé beaucoup de Java méthodes par une Scala méthodes que le spec/compilateur impose des restrictions arbitraires).

130
soc

Je voudrais citer Lukas Rytz (de ici ):

La raison en est que nous voulions un schéma de nommage 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 aurions besoin d'un schéma de dénomination différent. Mais nous voulons garder le code d'octet généré stable sur plusieurs exécutions de compilateur.

Une solution pour la future Scala pourrait être d'incorporer les noms de type des arguments non par défaut (ceux du 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: String)(b: Int = 42) = a + b
def foo(a: Int)   (b: Int = 42) = a + b

ce serait quelque chose comme:

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

Quelqu'un prêt à écrire une SIP ?

107
Eugen Labun

Il serait très difficile d'obtenir une spécification lisible et précise pour les interactions de 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 arriver. Mais ce n'est pas assez. Nous aurions besoin d'une spécification qui décide de tous les cas d'angle possibles. La résolution de surcharge est déjà très difficile à spécifier. L'ajout d'arguments par défaut dans le mix rendrait encore plus difficile. C'est pourquoi nous avons choisi de séparer les deux.

66
Martin Odersky

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, String], 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 ...

11
Landei

Ce qui a fonctionné pour moi, c'est de redéfinir (à la manière de Java) les méthodes de surcharge.

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"

Cela garantit au compilateur la résolution que vous souhaitez en fonction des paramètres actuels.

6
belka

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

Ce que tu veux vraiment:

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 = // ...

Contournement

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 ..."
2
Guillaume Massé

Un des scénarios possibles est


  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

Le compilateur sera confus quant à celui à appeler. Pour éviter d'autres dangers possibles, le compilateur autoriserait au plus une méthode surchargée avec des arguments par défaut.

Juste ma conjecture :-)

1
Shiva Wu

Ma compréhension est 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 discussions.

La spécification de l'argument nommé 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 du moins, ça ne marchera pas.

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

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