Подтвердить что ты не робот

Scala неизменяемая карта, когда нужно переходить на изменение?

Мой существующий случай использования довольно тривиален, либо изменчивая, либо неизменяемая Карта сделает трюк.

Имейте метод, который берет неизменяемую карту, которая затем вызывает сторонний API-метод, который также берет неизменяемую карту.

def doFoo(foo: String = "default", params: Map[String, Any] = Map()) {

  val newMap = 
    if(someCondition) params + ("foo" -> foo) else params

  api.doSomething(newMap)
}

Карта, о которой идет речь, обычно будет довольно небольшой, в лучшем случае может быть встроенный список экземпляров класса case, макс. Таким образом, опять же, предположим, что в этом случае маловероятно, чтобы в нем не было неизменяемости (т.е. Имело по существу 2 экземпляра Карты через новую копию newMap).

Тем не менее, это немного меня укусило, скопировав карту, чтобы получить новую карту с несколькими привязанными к ней символами k- > v.

Я мог бы пойти mutable и params.put("bar", bar) и т.д. для записей, которые я хочу использовать, а затем params.toMap, чтобы преобразовать в неизменяемый для вызова api, то есть вариант. но затем мне приходится импортировать и передавать измененные карты, что немного хлопотно по сравнению с тем, что происходит с неизменяемой картой Scala по умолчанию.

Итак, каковы общие рекомендации, когда оправдано/хорошая практика использования изменчивой карты над неизменяемыми Картами?

Спасибо

ИЗМЕНИТЬ Таким образом, кажется, что операция добавления на неизменяемом карте занимает почти постоянное время, подтверждая утверждение @dhg и @Nicolas о том, что полная копия не создана, что решает проблему для конкретного представленного случая.

4b9b3361

Ответ 1

В зависимости от неизменяемой реализации карты добавление нескольких записей может фактически не копировать всю исходную карту. Это одно из преимуществ подхода неизменяемой структуры данных: Scala попытается как можно меньше уйти с копированием.

Такое поведение проще всего увидеть с помощью List. Если у меня есть val a = List(1,2,3), то этот список сохраняется в памяти. Однако, если я добавлю дополнительный элемент типа val b = 0 :: a, я получаю новый 4-элементный List назад, но Scala не копировал список orignal a. Вместо этого мы просто создали одну новую ссылку, назвав ее b, и указали ей указатель на существующий List a.

Вы можете представить стратегии, подобные этой, для других видов коллекций. Например, если я добавлю один элемент в Map, коллекция может просто обернуть существующую карту, возвращаясь к ней, когда это необходимо, и все время предоставляя API, как если бы это был единственный Map.

Ответ 2

Использование изменчивого объекта само по себе плохо, оно становится плохой в функциональной среде программирования, где вы пытаетесь избежать побочных эффектов, сохраняя функции чистыми и объекты неизменяемыми.

Однако, если вы создаете изменяемый объект внутри функции и изменяете этот объект, функция по-прежнему чиста, если вы не освободите ссылку на этот объект вне функции. Допустимо иметь код типа:

def buildVector( x: Double, y: Double, z: Double ): Vector[Double] = {
    val ary = Array.ofDim[Double]( 3 )
    ary( 0 ) = x
    ary( 1 ) = y
    ary( 2 ) = z
    ary.toVector
}

Теперь я считаю, что этот подход полезен/рекомендуется в двух случаях: (1) Производительность, если создание и изменение неизменяемого объекта является узким местом всего вашего приложения; (2) Кодируемость кода, потому что иногда проще модифицировать сложный объект (вместо того, чтобы прибегать к линзам, молнии и т.д.)

Ответ 3

В дополнение к ответу dhg вы можете взглянуть на производительность коллекций scala. Если операция добавления/удаления не занимает линейного времени, она должна делать что-то еще, а не просто копировать всю структуру. (Обратите внимание, что обратное неверно: оно не должно зависеть от линейного времени, когда вы копируете всю структуру)

Ответ 4

Мне нравится использовать collections.maps как объявленные типы параметров (входные или возвращаемые значения), а не изменяемые или неизменяемые карты. Карты коллекций являются неизменяемыми интерфейсами, которые работают для обоих типов реализаций. Потребительский метод с использованием карты действительно не должен знать о реализации карты или о том, как она была построена. (Это ни в коем случае не его бизнес).

Если вы идете с тем, чтобы скрывать конкретную конструкцию карты (будь она изменчивой или неизменной) от потребителей, которые ее используют, вы все равно получаете по существу неизменяемую карту вниз по течению. И используя collection.Map как непреложный интерфейс, вы полностью удаляете всю неэффективность ".toMap", которую вы имели бы с потребителями, написанными для использования неизменяемых. Чтобы преобразовать полностью построенную карту в другую, просто соблюдать интерфейс, не поддерживаемый первым, на самом деле абсолютно ненужные накладные расходы, когда вы думаете об этом.

Я подозреваю, что через несколько лет мы рассмотрим три отдельных набора интерфейсов (изменяемые карты, неизменные карты и карты коллекций) и поймем, что 99% времени всего 2 действительно необходимы (изменяемые и коллекций) и что использование (к сожалению) неизменяемого интерфейса карты действительно добавляет много лишних накладных расходов для "Масштабируемого языка".