scala> val shares = Map("Apple" -> 23, "MicroSoft" -> 50, "IBM" -> 17)
shares: scala.collection.immutable.Map[java.lang.String,Int]
= Map(Apple -> 23, MicroSoft -> 50, IBM -> 17)
scala> val shareholders = shares map {_._1}
shareholders: scala.collection.immutable.Iterable[java.lang.String]
= List(Apple, MicroSoft, IBM)
scala> val newShares = shares map {case(k, v) => (k, 1.5 * v)}
newShares: scala.collection.immutable.Map[java.lang.String,Double]
= Map(Apple -> 34.5, MicroSoft -> 75.0, IBM -> 25.5)
От этого примера это походит map
метод перегружается на типе возврата. Перегрузка на типе возврата не является возможным правом? Кто-то объяснил бы, что продолжается здесь?
Возможно, вы захотите взглянуть на этот вопрос о сигнатуре map
, который имеет параметрический возвращаемый тип That
. Ответ Мартина Одерски объясняет, как (и почему) могут быть возвращены различные типы.
Обратите внимание, что возвращаемый тип не связан каким-либо образом с Traversable
или даже с параметризованным типом цели. Например:
IndexedSeq[Char].map --> String
Как видно из StringOps
map
не перегружен при возврате типа. Вместо этого существует один метод с абстрактным возвращаемым типом.
def map[B, That](f: A => B)(implicit bf: CanBuildFrom[Repr, B, That]): That
В байт-коде это стирается в Object
:
public abstract java.lang.Object map(scala.Function1, scala.collection.generic.CanBuildFrom);
Шаблон лучше всего описан в статье Борьба с битовой гнилью с помощью типов
Вот пример из игры, которую я пишу. Он переопределяет возвращаемый тип:
def consumeItem(item: ConsumableItem) {
executePartyAction[Unit](_.inventory.consumeItem(item, this))
}
def craftItem[ItemType <: Item](recipe: Recipe[ItemType]) = {
executePartyAction[ItemType](_.inventory.craftItem(recipe, this))
}
private def executePartyAction[ReturnType](partyAction: Party => ReturnType): ReturnType = {
party match {
case Some(party) => partyAction(party)
case None => throw new PlayerCharacterMustBelongToAParty
}
}
Хотя истина сложна, я собираюсь привести рассуждение, в котором применяется тот же принцип, но все сильно упрощается, чтобы вы могли увидеть логику в языке.
В Scala нет перегрузки на основе возвращаемого типа. (Даже в паттерн-сопоставлении, где "параметры" паттерна совпадают с возвращаемым типом unapply
, что позволяет использовать возвращаемый тип для разрешения перегрузки).
Вы не перегружаете метод map
на основе типа возврата - вы перегружаете его на основе типа возврата функции, переданной в качестве параметра. Изменение типа возврата изменяет тип возврата параметра, поэтому вы, по сути, перегружаете метод на основе различных типов параметров. Таким образом, логически рассуждая, вы имеете нечто эквивалентное следующей ситуации:
trait Seq[X]{
def map[Y](func: X => Y) : Seq[Y]
def map[Y,Z](func: X => Tuple2[Y,Z]) : Map[Y,Z]
}
Тип возврата функции, переданной в map, определяет, какая версия будет вызвана.
реальная реализация просто делает эту логику более общей и расширяемой на множество типов коллекций, которые есть в библиотеке Scala, и на множество других типов коллекций, которые еще не написаны.
Это не то, что происходит в данном случае, но на самом деле да, перегрузка по возвращаемому типу поддерживается JVM . Это доступно в Scala для методов, которые имеют разные универсальные типы аргументов, которые стираются до одного и того же типа. Пример, приведенный по ссылке:
object Overload{
def foo(xs : String*) = "foo"; // after erasure is foo:(Seq)String
def foo(xs : Int*) = 3; // after erasure is foo:(Seq)Int
}