Как создать подкласс Scala immutable.Map с параметрами фиксированного типа? - PullRequest
4 голосов
/ 05 мая 2011

Я не могу понять, как бороться с переопределением «+» в неизменяемой карте, если карта может хранить только инвариантный тип для своих значений.

Что-то вроде:

class FixedMap(val impl : Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {
    // This should return FixedMap if B1 is Int, and Map[String,B1]
    // if B1 is a superclass of Int; but there's no way to do that.
    // It is possible to return FixedMap here but then you have to
    // throw at runtime if B1 is not Int
    override def +[B1 >: Int](kv : (String, B1)) : Map[String, B1] = {
        kv match {
            case (k, v : Int) =>
                new FixedMap(impl + Pair(k, v))
            case _ =>
                impl + kv
        }
    }
    // ...
}

Я бы хотел, чтобы это работало так же, как методы, использующие CanBuildFrom, и всегда сохраняйте исходный тип, если это возможно. Есть ли способ? Или подклассы Map всегда должны оставлять тип значения в качестве параметра типа?

Вот полный скомпилированный пример:

import scala.collection.immutable

// pointless class that wraps another map and adds one method
class FixedMap(val impl : Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {

    override val empty : FixedMap = FixedMap.empty

    // This should return FixedMap if B1 is Int, and Map[String,B1]
    // if B1 is a superclass of Int; but there's no way to do that.
    // It is possible to return FixedMap here but then you have to
    // throw at runtime if B1 is not Int
    override def +[B1 >: Int](kv : (String, B1)) : Map[String, B1] = {
        kv match {
            case (k, v : Int) =>
                new FixedMap(impl + Pair(k, v))
            case _ =>
                impl + kv
        }
    }

    override def -(key : String) : FixedMap = {
        new FixedMap(impl - key)
    }

    override def get(key : String) : Option[Int] = {
        impl.get(key)
    }

    override def iterator : Iterator[(String, Int)] = {
        impl.iterator
    }

    def somethingOnlyPossibleOnFixedMap() = {
        println("FixedMap says hi")
    }
}

object FixedMap {
    val empty : FixedMap = new FixedMap(Map.empty)
}

object TestIt {
    val empty = FixedMap.empty
    empty.somethingOnlyPossibleOnFixedMap()
    val one = empty + Pair("a", 1)
    // Can't do the below because one is a Map[String,Int] not a FixedMap
    // one.somethingOnlyPossibleOnFixedMap()
}

Ответы [ 3 ]

2 голосов
/ 06 мая 2011

Вот что я бы попробовал:

class FixedMap(val impl: immutable.Map[String, Int])
    extends immutable.Map[String, Int] with immutable.MapLike[String, Int, FixedMap] {

  override def +[B1 >: Int](kv: (String, B1)): immutable.Map[String, B1] = impl + kv

  def +(kv: (String, Int))(implicit d: DummyImplicit): FixedMap = new FixedMap(impl + kv)

  // ...

}

Вы правы: вы не можете переопределить уже существующее +, поэтому вы должны оставить его там, иначе ваш подкласс не сможет делать то, что может делать суперкласс, что нарушает Принцип подстановки Лискова . Но вы можете добавить дополнительный метод с точными аргументами, которые вам нужны (и вам не нужен CanBuildFrom в данном конкретном случае).

Единственная проблема заключается в том, что новый метод имеет такое же стирание типов, что и тот, который вы пытались переопределить, но который имеет несовместимую сигнатуру. Чтобы решить эту проблему, вы можете добавить DummyImplicit - определенный в Predef как класс, для которого всегда доступно неявное значение. Его основное назначение - обойти стирание типа в случае перегрузки, подобной этой.

Обратите внимание, что статический тип карты, на которой вы хотите вызвать перегруженный метод, должен быть FixedMap, чтобы это работало. Если объект имеет тип времени выполнения FixedType, но статически типизирован к обычному Map[String, Int], компилятор не будет вызывать ваш новый перегруженный метод.

1 голос
/ 05 мая 2011

Можно реализовать то, что вы хотите, используя CanBuildFrom.Вопрос - вы действительно хотите / должны это сделать?Есть несколько похожих вопросов в SO, вот один из них (надеюсь, вы найдете там ответ):

Расширение коллекций Scala

Обычно Scala предоставляет достаточно инструментовИзбегайте этого (расширение коллекций).И вам действительно нужна веская причина, чтобы начать с этого.

0 голосов
/ 06 мая 2011

Похоже, что это работает (лучше всего я могу сказать), если вы добавите еще одну + перегрузку:

def +(kv : (String, Int))(implicit bf : CanBuildFrom[FixedMap, (String, Int), FixedMap]) : FixedMap = {
    val b = bf(empty)
    b ++= this
    b += kv
    b.result
}

Проблема, с которой я сталкивался раньше, была вызвана возвратом FixedMap из переопределенного +, что предотвратило любое обновление до общей карты. Я предполагаю, что это позволило неявное преобразование пары "+" в работу. Но если вы исправите этот переопределенный метод +, чтобы снова вернуть Map [String, B1], вы не сможете больше использовать неявное преобразование для значения пары. У компилятора нет возможности узнать, перейти ли на карту суперкласса, т. Е. Map [String, Any], или неявно преобразовать в Int, чтобы придерживаться FixedMap. Интересно, что тип возвращаемого значения метода изменяется независимо от того, используются ли неявные преобразования; Я предполагаю, что при заданном типе возврата FixedMap компилятор может сделать вывод, что B1 всегда просто B (или что-то в этом роде!).

В любом случае, похоже, это моя ошибка; вы просто не можете использовать неявное преобразование для пары ._2, передаваемой +, будучи совместимым с интерфейсом Map, даже концептуально. Теперь, когда я отказался от этого, я думаю, что перегруженный + будет работать нормально.

...