Расширение встроенных коллекций, проблемы со встроенными методами - PullRequest
2 голосов
/ 13 августа 2010

Я новичок в Scala, так что простите меня, если это глупый вопрос, но здесь идет ...

Представьте, что я хочу создать расширенный тип карты, который включает в себя дополнительные методы.Я вижу несколько способов сделать это.Первый будет с композицией:

class Path[V](val m: Map[V, Int]) {
  // Define my methods
}

Другой будет через наследование, например

class Path[V] extends Map[V, Int] {
// Define my methods
}

Наконец, я также рассмотрел маршрут "trait", например

trait Path[V] extends Map[V, Int] {
// Define my methods
}

Композиция немного неловкая, потому что вам постоянно приходится ссылаться на вещи внутри.Наследование является довольно естественным, но для меня есть морщина (больше за секунду).Черты кажутся действительно элегантным способом сделать это, и с помощью конструкции «с» это действительно приятно, но у меня также есть проблема.

Я сталкиваюсь с морщинами с такими методами, как ++.Они возвращают новую карту.Итак, допустим, что «мой метод», о котором говорилось выше, хочет добавить что-то на карту (просто пример, я знаю, что карта уже имеет это), например,

trait Path[V] extends Map[V,Int] {
    def addOne(v: V, i: Int): Path[V] = this + (v -> i)
}

Это генерирует ошибку, потому что тип возвратаэто не путь [V].Теперь я знаю, что могу использовать «с» в новом экземпляре, чтобы добавить черту Path [V].Но я не контролирую строительство новой карты здесь.Есть ли способ добавить черту Path [V]?Я думал о создании новой неизменяемой карты, которая была бы предварительно заполнена, а затем помечена тегом «with Path [V]», но такого конструктора я не могу использовать для создания предварительно заполненной карты.

IПодозреваю (хотя я не подтвердил это), что у меня будет аналогичная проблема с использованием наследования.Я мог бы добавить новый метод, чтобы добавить новую запись на карту, но я бы не получил "Path [V]", который я хочу.Композиционный подход, кажется, единственный путь сюда.

Надеюсь, это понятно.Комментарии?

1 Ответ

6 голосов
/ 13 августа 2010

Возможно, самый простой способ сделать это - использовать черту MapProxy:

import collection._

class Path[V](val self: Map[V, Int]) extends MapProxy[V, Int] {
   // without the companion object below
   def addOne(v: V, i: Int): Path[V] = new Path(this + (v -> i))
   // with the companion object below
   def addOne(v: V, i: Int): Path[V] = this + (v -> i)
   // other methods
}

// this companion object is not strictly necessary, but is useful:
object Path {
   implicit def map2path[V](map: Map[V, Int]): Path[V] = new Path(map)
}

Это устраняет неловкость с композиционным подходом, позволяя вам обрабатывать Path, как если бы это было Map:

scala> new Path(Map("a" -> 1)) + ("b" -> 2)
res1: scala.collection.Map[java.lang.String,Int] = Map((a,1), (b,2))

scala> new Path(Map("a" -> 1)).get("a")
res2: Option[Int] = Some(1)

Если вы включите неявное преобразование из Map в Path (определенное в сопутствующем объекте Path), то вы также можете трактовать Map, как если бы это было Path:

scala> Map("a" -> 1).addOne("b", 2)
res3: Path[java.lang.String] = Map((a,1), (b,2))

Существует похожая черта SeqProxy для добавления поведения к Seq.

В более общем случае решение состоит в том, чтобы использовать шаблон pimp my library . Этот связанный вопрос имеет пример.

...