Skip to content

Commit dfc4712

Browse files
committed
Add mutable and concurrent Map to stdlib
1 parent 9388f29 commit dfc4712

File tree

3 files changed

+466
-1
lines changed

3 files changed

+466
-1
lines changed

tests/pos-special/stdlib/collection/Map.scala

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,8 @@ trait Map[K, +V]
2424
extends Iterable[(K, V)]
2525
with MapOps[K, V, Map, Map[K, V]]
2626
with MapFactoryDefaults[K, V, Map, Iterable]
27-
with Equals {
27+
with Equals
28+
with Pure {
2829

2930
def mapFactory: scala.collection.MapFactory[Map] = Map
3031

Lines changed: 193 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,193 @@
1+
/*
2+
* Scala (https://www.scala-lang.org)
3+
*
4+
* Copyright EPFL and Lightbend, Inc.
5+
*
6+
* Licensed under Apache License 2.0
7+
* (http://www.apache.org/licenses/LICENSE-2.0).
8+
*
9+
* See the NOTICE file distributed with this work for
10+
* additional information regarding copyright ownership.
11+
*/
12+
13+
package scala
14+
package collection.concurrent
15+
16+
import language.experimental.captureChecking
17+
import scala.annotation.tailrec
18+
19+
/** A template trait for mutable maps that allow concurrent access.
20+
*
21+
* $concurrentmapinfo
22+
*
23+
* @see [[https://docs.scala-lang.org/overviews/collections-2.13/concrete-mutable-collection-classes.html#concurrent_maps "Scala's Collection Library overview"]]
24+
* section on `Concurrent Maps` for more information.
25+
*
26+
* @tparam K the key type of the map
27+
* @tparam V the value type of the map
28+
*
29+
* @define Coll `concurrent.Map`
30+
* @define coll concurrent map
31+
* @define concurrentmapinfo
32+
* This is a base trait for all Scala concurrent map implementations. It
33+
* provides all of the methods a `Map` does, with the difference that all the
34+
* changes are atomic. It also describes methods specific to concurrent maps.
35+
*
36+
* '''Note''': The concurrent maps do not accept `'''null'''` for keys or values.
37+
*
38+
* @define atomicop
39+
* This is an atomic operation.
40+
*/
41+
trait Map[K, V] extends scala.collection.mutable.Map[K, V] {
42+
43+
/**
44+
* Associates the given key with a given value, unless the key was already
45+
* associated with some other value.
46+
*
47+
* $atomicop
48+
*
49+
* @param k key with which the specified value is to be associated with
50+
* @param v value to be associated with the specified key
51+
* @return `Some(oldvalue)` if there was a value `oldvalue` previously
52+
* associated with the specified key, or `None` if there was no
53+
* mapping for the specified key
54+
*/
55+
def putIfAbsent(k: K, v: V): Option[V]
56+
57+
/**
58+
* Removes the entry for the specified key if it's currently mapped to the
59+
* specified value.
60+
*
61+
* $atomicop
62+
*
63+
* @param k key for which the entry should be removed
64+
* @param v value expected to be associated with the specified key if
65+
* the removal is to take place
66+
* @return `true` if the removal took place, `false` otherwise
67+
*/
68+
def remove(k: K, v: V): Boolean
69+
70+
/**
71+
* Replaces the entry for the given key only if it was previously mapped to
72+
* a given value.
73+
*
74+
* $atomicop
75+
*
76+
* @param k key for which the entry should be replaced
77+
* @param oldvalue value expected to be associated with the specified key
78+
* if replacing is to happen
79+
* @param newvalue value to be associated with the specified key
80+
* @return `true` if the entry was replaced, `false` otherwise
81+
*/
82+
def replace(k: K, oldvalue: V, newvalue: V): Boolean
83+
84+
/**
85+
* Replaces the entry for the given key only if it was previously mapped
86+
* to some value.
87+
*
88+
* $atomicop
89+
*
90+
* @param k key for which the entry should be replaced
91+
* @param v value to be associated with the specified key
92+
* @return `Some(v)` if the given key was previously mapped to some value `v`, or `None` otherwise
93+
*/
94+
def replace(k: K, v: V): Option[V]
95+
96+
override def getOrElseUpdate(key: K, op: => V): V = get(key) match {
97+
case Some(v) => v
98+
case None =>
99+
val v = op
100+
putIfAbsent(key, v) match {
101+
case Some(ov) => ov
102+
case None => v
103+
}
104+
}
105+
106+
/**
107+
* Removes the entry for the specified key if it's currently mapped to the
108+
* specified value. Comparison to the specified value is done using reference
109+
* equality.
110+
*
111+
* Not all map implementations can support removal based on reference
112+
* equality, and for those implementations, object equality is used instead.
113+
*
114+
* $atomicop
115+
*
116+
* @param k key for which the entry should be removed
117+
* @param v value expected to be associated with the specified key if
118+
* the removal is to take place
119+
* @return `true` if the removal took place, `false` otherwise
120+
*/
121+
// TODO: make part of the API in a future version
122+
private[collection] def removeRefEq(k: K, v: V): Boolean = remove(k, v)
123+
124+
/**
125+
* Replaces the entry for the given key only if it was previously mapped to
126+
* a given value. Comparison to the specified value is done using reference
127+
* equality.
128+
*
129+
* Not all map implementations can support replacement based on reference
130+
* equality, and for those implementations, object equality is used instead.
131+
*
132+
* $atomicop
133+
*
134+
* @param k key for which the entry should be replaced
135+
* @param oldValue value expected to be associated with the specified key
136+
* if replacing is to happen
137+
* @param newValue value to be associated with the specified key
138+
* @return `true` if the entry was replaced, `false` otherwise
139+
*/
140+
// TODO: make part of the API in a future version
141+
private[collection] def replaceRefEq(k: K, oldValue: V, newValue: V): Boolean = replace(k, oldValue, newValue)
142+
143+
/**
144+
* Update a mapping for the specified key and its current optionally-mapped value
145+
* (`Some` if there is current mapping, `None` if not).
146+
*
147+
* If the remapping function returns `Some(v)`, the mapping is updated with the new value `v`.
148+
* If the remapping function returns `None`, the mapping is removed (or remains absent if initially absent).
149+
* If the function itself throws an exception, the exception is rethrown, and the current mapping is left unchanged.
150+
*
151+
* If the map is updated by another concurrent access, the remapping function will be retried until successfully updated.
152+
*
153+
* @param key the key value
154+
* @param remappingFunction a partial function that receives current optionally-mapped value and return a new mapping
155+
* @return the new value associated with the specified key
156+
*/
157+
override def updateWith(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = updateWithAux(key)(remappingFunction)
158+
159+
@tailrec
160+
private def updateWithAux(key: K)(remappingFunction: Option[V] => Option[V]): Option[V] = {
161+
val previousValue = get(key)
162+
val nextValue = remappingFunction(previousValue)
163+
previousValue match {
164+
case Some(prev) => nextValue match {
165+
case Some(next) => if (replaceRefEq(key, prev, next)) return nextValue
166+
case _ => if (removeRefEq(key, prev)) return None
167+
}
168+
case _ => nextValue match {
169+
case Some(next) => if (putIfAbsent(key, next).isEmpty) return nextValue
170+
case _ => return None
171+
}
172+
}
173+
updateWithAux(key)(remappingFunction)
174+
}
175+
176+
private[collection] def filterInPlaceImpl(p: (K, V) => Boolean): this.type = {
177+
val it = iterator
178+
while (it.hasNext) {
179+
val (k, v) = it.next()
180+
if (!p(k, v)) removeRefEq(k, v)
181+
}
182+
this
183+
}
184+
185+
private[collection] def mapValuesInPlaceImpl(f: (K, V) => V): this.type = {
186+
val it = iterator
187+
while (it.hasNext) {
188+
val (k, v) = it.next()
189+
replaceRefEq(k, v, f(k, v))
190+
}
191+
this
192+
}
193+
}

0 commit comments

Comments
 (0)