идиоматическое получение или еще обновление для immutable.Map?

Каков идиоматический способ getOrElseUpdate для экземпляров immutable.Map ?. Я использую приведенный ниже фрагмент, но он кажется подробным и неэффективным.

var map = Map[Key, Value]()

def foo(key: Key) = {
  val value = map.getOrElse(key, new Value)
  map += key -> value
  value
}

person IttayD    schedule 08.12.2010    source источник
comment
map += key -> value - ›Возможно, вы имеете в виду mutable.Map карту?   -  person Vasil Remeniuk    schedule 08.12.2010
comment
Я думаю, вы имеете в виду изменчивую карту !? Вы используете + = в своем коде, который не работает для immutable.Map. Для mutable.Map есть getOrElseUpdate ().   -  person michid    schedule 08.12.2010
comment
@ Василий Ременюк, @michid: Конечно, работает. Когда нет метода '+ =', компилятор преобразует выражение в 'map = map + key - ›value'. Я обновил вопрос, чтобы было ясно, что карта - это var   -  person IttayD    schedule 08.12.2010


Ответы (4)


Позвольте мне резюмировать вашу проблему:

  • Вы хотите вызвать метод для неизменяемой структуры данных
  • Вы хотите, чтобы он возвращал какое-то значение и переназначал var
  • Because the data structure is immutable, you’ll need to
    • return a new immutable data structure, or
    • выполнить присваивание внутри метода, используя прилагаемое закрытие

Итак, ваша подпись должна иметь вид

def getOrElseUpdate(key: K): Tuple2[V, Map[K,V]]
//... use it like
val (v, m2) = getOrElseUpdate(k)
map = m2

or

def getOrElseUpdate(key: K, setter: (Map[K,V]) => Unit): V
//... use it like
val v = getOrElseUpdate(k, map = _)

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

person Debilski    schedule 08.12.2010
comment
Я предполагаю, что из всех ответов это означает, что нет идиоматического способа (кроме предложения @Daniel полностью работать с неизменяемыми объектами) - person IttayD; 09.12.2010
comment
Проблема в том, что вы хотите переназначить свою переменную, делая что-то еще. Невозможно сделать это изнутри другого метода без помощи (например, он становится уродливым). Для этого вам понадобится надлежащая поддержка передачи по ссылке (что возможно только с некоторыми хаками в Scala). - person Debilski; 09.12.2010
comment
На самом деле получить семантику передачи по ссылке в Scala довольно просто: case class Var[T](var ref: T) { def apply() = ref; def update(v: T) = ref = v }. Идиоматический? Нет. - person Aaron Novstrup; 09.12.2010
comment
@Aaron Novstrup: Но для этого требуется, чтобы пользователь активно применил некоторую оболочку (и затем развернул ее), поэтому это невозможно сделать без помощи внутри некоторого метода. В большинстве случаев предоставление замыкания установщика (m = _) будет лучшим решением для вещей, для которых вам понадобится передача по ссылке. - person Debilski; 09.12.2010
comment
Обертывание / разворачивание довольно прозрачно: например, val v = new Var(1); v() = 2; println(v()), но я согласен, что закрытие - более простое решение. - person Aaron Novstrup; 09.12.2010

Я бы, наверное, реализовал такой метод getOrElseUpdated:

def getOrElseUpdated[K, V](m: Map[K, V], key: K, op: => V): (Map[K, V], V) =
  m.get(key) match {
    case Some(value) => (m, value)
    case None => val newval = op; (m.updated(key, newval), newval)
  }

который либо возвращает исходную карту, если m имеет сопоставление для key, либо другую карту с добавленным сопоставлением key -> op. Определение этого метода аналогично определению getOrElseUpdate из mutable.Map.

person Frank S. Thomas    schedule 30.04.2011
comment
вы единственный, кто действительно ответил на вопрос :-) - person ib84; 05.03.2013

Такого пути нет - мутация (обновление) карты, когда вы получаете значение карты, является побочным эффектом (что противоречит неизменяемости / функциональному стилю программирования).

Если вы хотите создать новую неизменяемую карту со значением по умолчанию, если другое значение для указанного ключа не существует, вы можете сделать следующее:

map + (key -> map.getOrElse(key, new Value)) 
person Vasil Remeniuk    schedule 08.12.2010
comment
Ваш код - это строки 1-2 в моем 'foo', но он возвращает карту, а не значение - person IttayD; 08.12.2010

Почему бы не использовать withDefault или withDefaultValue, если у вас неизменяемая карта?

person Daniel C. Sobral    schedule 08.12.2010
comment
withDefault и withDefaultValue вернут значение по умолчанию, но не обновят карту. - person IttayD; 08.12.2010
comment
@IttayD И какая разница? - person Daniel C. Sobral; 08.12.2010
comment
@Daniel withDefault и withDefaultValue не будут иметь той же семантики, что и код @ IttayD, приведенный выше, в отношении идентичности объекта. withDefault создаст новые, потенциально неравные значения при многократном извлечении данного ключа. withDefaultValue вернет конкретное значение при извлечении разных ключей. - person Aaron Novstrup; 08.12.2010
comment
@Aaron True, что может иметь или не иметь отношения. Вот моя точка зрения: есть ли какая-то конкретная причина, по которой он не может работать? Не следует отказываться от этого только потому, что это было не то решение, которое вы изначально предвидели. - person Daniel C. Sobral; 08.12.2010
comment
@Daniel, разница в том, что если Value является изменяемым, я хочу, чтобы та же ссылка, которая была возвращена, была возвращена при следующем вызове. Даже если Value является неизменным, я не хочу создавать его при каждом вызове. - person IttayD; 09.12.2010
comment
@IttayD Значения не создаются заново с помощью withDefaultValue, а withDefault может легко использовать функцию запоминания. - person Daniel C. Sobral; 09.12.2010
comment
@Daniel, так что в withDefault мне нужно было бы использовать карту, чтобы вернуть меня на круги своя. - person IttayD; 10.12.2010
comment
@IttayD Но на этой карте вы можете просто использовать withDefault с функцией запоминания! ;-) - person Aaron Novstrup; 12.12.2010