Заметив, что мой код был по существу итерированием по списку и обновлением значения на Карте, я сначала создал тривиальный вспомогательный метод, который принял функцию для преобразования значения карты и возврата обновленной карты. По мере развития программы он получил несколько других функций Map-transform, поэтому было естественным превратить его в неявный класс значений, который добавляет методы к scala.collection.immutable.Map[A, B]
. Эта версия работает нормально.Избегание понижений в общих классах
Однако нет ничего о методах, требующих конкретной реализации карты, и они, похоже, будут применяться к scala.collection.Map[A, B]
или даже к MapLike
. Поэтому я хотел бы, чтобы он был общим для типа карты, а также для ключей и значений. Здесь все идет грушевидно.
Моя текущая итерация выглядит следующим образом:
implicit class RichMap[A, B, MapType[A, B] <: collection.Map[A, B]](
val self: MapType[A, B]
) extends AnyVal {
def updatedWith(k: A, f: B => B): MapType[A, B] =
self updated (k, f(self(k)))
}
Этот код не компилируется, потому что self updated (k, f(self(k)))
ISA scala.collection.Map[A, B]
, который не является MapType[A, B]
. Другими словами, возвращаемый тип self.updated
выглядит так, как если бы тип self
был привязан к верхнему типу, а не к объявленному типу.
я могу «зафиксировать» код с опущенными:
def updatedWith(k: A, f: B => B): MapType[A, B] =
self.updated(k, f(self(k))).asInstanceOf[MapType[A, B]]
Это не чувствует себя удовлетворительно, потому что это понижающее приведение код запах и указывает на неправильное использование системы типа. В этом конкретном случае казалось бы, что значение всегда будет типа cast-to и что вся программа компилируется и работает правильно с этим downcast, поддерживает это представление, но оно все еще пахнет.
Итак, есть ли лучший способ написать этот код, чтобы иметь правильные идентификаторы scalac без использования downcast, или это ограничение компилятора, а также сокращение?
[Отредактировано добавить следующее.]
Мой код, который использует этот метод является несколько более сложным и грязный, как я до сих пор изучает несколько идей, но пример минимальный случай является вычисление распределение частот в качестве побочного эффекта с кодом примерно так:
var counts = Map.empty[Int, Int] withDefaultValue 0
for (item <- items) {
// loads of other gnarly item-processing code
counts = counts updatedWith (count, 1 + _)
}
Есть три ответа на мой вопрос, на момент написания.
В любом случае, просто позвольте updatedWith
вернуть scala.collection.Map[A, B]
. По сути, мне нужна моя оригинальная версия, которая приняла и вернула immutable.Map[A, B]
, и делает тип менее конкретным. Другими словами, он все еще недостаточно общий и устанавливает политику, по которой используются вызывающие пользователи. Я могу, конечно, изменить тип в объявлении counts
, но это также запах кода для работы с библиотекой, возвращающей неправильный тип, и все, что она действительно делает, - это перемещение вниз в код вызывающего. Так что мне совсем не нравится этот ответ.
Остальные два являются вариантами на CanBuildFrom
и строителями, в которых они по существу перебирают карту для создания модифицированной копии. Один из них заключает в себе модифицированный метод updated
, тогда как другой вызывает оригинал updated
и добавляет его в конструктор и, как представляется, делает дополнительную временную копию. Оба являются хорошими ответами, которые решают проблему корректности типа, хотя тот, который избегает дополнительной копии, лучше из двух с точки зрения производительности, и я предпочитаю это по этой причине. Другой, однако, короче и, возможно, более четко показывает намерение.
В случае гипотетической неизменяемой Карты, которая разделяет большие деревья в аналогичном ключе на List, это копирование приведет к нарушению совместного использования и снижению производительности, и поэтому было бы предпочтительнее использовать существующие modified
без выполнения копий. Тем не менее, неизменяемые карты Scala, похоже, не делают этого, и поэтому копирование (один раз) представляется прагматичным решением, которое вряд ли будет иметь какое-либо значение на практике.
"гипотетический непреложным Map" Почему гипотетическая? HashMap имеет [гарантированное] (http://docs.scala-lang.org/overviews/collections/performance-characteristics.html) добавление постоянного времени (которое эффективно обновляется), что может быть достигнуто только путем совместного использования больших частей существующих данных. – Suma