Skip to content
This repository was archived by the owner on Dec 22, 2021. It is now read-only.
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -210,6 +210,7 @@ For more information, see the [CONTRIBUTING](CONTRIBUTING.md) file.
- [x] `tail`
- [x] `take` / `takeRight` / `takeWhile`
- [x] `updated`
- [x] `withDefault` / `withDefaultValue`

### Transformations to collections that can have a different element type

Expand Down
49 changes: 47 additions & 2 deletions collections/src/main/scala/strawman/collection/immutable/Map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,30 @@ import scala.{Any, Boolean, `inline`, Int, None, NoSuchElementException, Nothing
trait Map[K, +V]
extends Iterable[(K, V)]
with collection.Map[K, V]
with MapOps[K, V, Map, Map[K, V]]
with MapOps[K, V, Map, Map[K, V]] {

/** The same map with a given default function.
* Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc.
* are not affected by `withDefault`.
*
* Invoking transformer methods (e.g. `map`) will not preserve the default value.
*
* @param d the function mapping keys to values, used for non-present keys
* @return a wrapper of the map with a default value
*/
def withDefault[V1 >: V](d: K => V1): Map[K, V1] = new Map.WithDefault[K, V1](this, d)

/** The same map with a given default value.
* Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc.
* are not affected by `withDefaultValue`.
*
* Invoking transformer methods (e.g. `map`) will not preserve the default value.
*
* @param d default value used for non-present keys
* @return a wrapper of the map with a default value
*/
def withDefaultValue[V1 >: V](d: V1): Map[K, V1] = new Map.WithDefault[K, V1](this, x => d)
}

/** Base trait of immutable Maps implementations
*
Expand Down Expand Up @@ -93,6 +116,29 @@ trait MapOps[K, +V, +CC[X, +Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]
*/
object Map extends MapFactory[Map] {

final class WithDefault[K, +V](underlying: Map[K, V], d: K => V) extends Map[K, V] {
// These factory methods will lose the default value
def iterableFactory = underlying.iterableFactory
def mapFactory: MapFactory[Map] = underlying.mapFactory
protected[this] def mapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)]): Map[K2,V2] = mapFactory.from(it)

// Specific building will keep the default but may lose the precise underlying type because our own V can be
// a supertype of the underlying collection's V so we cannot rebuild with potentially new values that are not
// valid for the underlying collection.
protected[this] def fromSpecificIterable(coll: collection.Iterable[(K, V)]): Map[K,V] = new WithDefault[K, V](mapFactory.from(coll), d)
protected[this] def newSpecificBuilder(): mutable.Builder[(K, V), Map[K,V]] =
mapFactory.newBuilder[K, V]().mapResult(new WithDefault[K, V](_, d))
override def size = underlying.size
def get(key: K) = underlying.get(key)
def iterator() = underlying.iterator()
override def default(key: K): V = d(key)
override def empty = new WithDefault(underlying.empty, d)
override def updated[V1 >: V](key: K, value: V1): WithDefault[K, V1] = new WithDefault[K, V1](underlying.updated[V1](key, value), d)
override def remove (key: K): WithDefault[K, V] = new WithDefault(underlying - key, d)
override def withDefault[V1 >: V](d: K => V1): immutable.Map[K, V1] = new WithDefault[K, V1](underlying, d)
override def withDefaultValue[V1 >: V](d: V1): immutable.Map[K, V1] = new WithDefault[K, V1](underlying, x => d)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why do we need to override withDefault and withDefaultValue? The implementation is the same as the base implementation.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No, it's not. It avoids layering several WithDefault on top of each other.

}

def empty[K, V]: Map[K, V] = EmptyMap.asInstanceOf[Map[K, V]]

def from[K, V](it: collection.IterableOnce[(K, V)]): Map[K, V] =
Expand Down Expand Up @@ -225,5 +271,4 @@ object Map extends MapFactory[Map] {
f((key1, value1)); f((key2, value2)); f((key3, value3)); f((key4, value4))
}
}

}
54 changes: 52 additions & 2 deletions collections/src/main/scala/strawman/collection/mutable/Map.scala
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,28 @@ trait Map[K, V]
old
}
*/

/** The same map with a given default function.
* Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc.
* are not affected by `withDefaultValue`.
*
* Invoking transformer methods (e.g. `map`) will not preserve the default value.
*
* @param d the function mapping keys to values, used for non-present keys
* @return a wrapper of the map with a default value
*/
def withDefault(d: K => V): Map[K, V] = new Map.WithDefault[K, V](this, d)

/** The same map with a given default value.
* Note: The default is only used for `apply`. Other methods like `get`, `contains`, `iterator`, `keys`, etc.
* are not affected by `withDefaultValue`.
*
* Invoking transformer methods (e.g. `map`) will not preserve the default value.
*
* @param d default value used for non-present keys
* @return a wrapper of the map with a default value
*/
def withDefaultValue(d: V): Map[K, V] = new Map.WithDefault[K, V](this, x => d)
}

/**
Expand Down Expand Up @@ -131,15 +153,43 @@ trait MapOps[K, V, +CC[X, Y] <: MapOps[X, Y, CC, _], +C <: MapOps[K, V, CC, C]]
coll -= elem
this
}

}

/**
* $factoryInfo
* @define coll mutable map
* @define Coll `mutable.Map`
*/
object Map extends MapFactory.Delegate[Map](HashMap)
object Map extends MapFactory.Delegate[Map](HashMap) {
final class WithDefault[K, V](underlying: Map[K, V], d: K => V) extends Map[K, V] {
// These factory methods will lose the default value
override def iterableFactory = underlying.iterableFactory
def mapFactory: MapFactory[Map] = underlying.mapFactory
protected[this] def mapFromIterable[K2, V2](it: collection.Iterable[(K2, V2)]): Map[K2,V2] = mapFactory.from(it)

// Specific building will keep the default but may lose the precise underlying type because our own V can be
// a supertype of the underlying collection's V so we cannot rebuild with potentially new values that are not
// valid for the underlying collection.
protected[this] def fromSpecificIterable(coll: collection.Iterable[(K, V)]): Map[K,V] = new WithDefault[K, V](mapFactory.from(coll), d)
protected[this] def newSpecificBuilder(): mutable.Builder[(K, V), Map[K,V]] =
mapFactory.newBuilder[K, V]().mapResult(new WithDefault[K, V](_, d))
override def size = underlying.size
def get(key: K) = underlying.get(key)
def iterator() = underlying.iterator()
override def default(key: K): V = d(key)
override def empty = new WithDefault(underlying.empty, d)

def clear(): Unit = underlying.clear()
def add(elem: (K, V)): this.type = { underlying.add(elem); this }
def subtract(elem: K): this.type = { underlying.subtract(elem); this }
override def put(key: K, value: V): Option[V] = underlying.put(key, value)
override def update(key: K, value: V): Unit = underlying.update(key, value)
override def remove(key: K): Option[V] = underlying.remove(key)

override def withDefault(d: K => V): Map[K, V] = new WithDefault[K, V](underlying, d)
override def withDefaultValue(d: V): Map[K, V] = new WithDefault[K, V](underlying, x => d)
}
}

/** Explicit instantiation of the `Map` trait to reduce class file size in subclasses. */
abstract class AbstractMap[A, B] extends strawman.collection.AbstractMap[A, B] with Map[A, B]
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package strawman.collection.immutable

import strawman.collection.Hashing

import org.junit.Assert.assertEquals
import org.junit.Test
import org.junit.runner.RunWith
Expand Down Expand Up @@ -47,4 +46,12 @@ class HashMapTest {
}
assertEquals(expected, mergedWithMergeFunction)
}

@Test
def testWithDefaultValue: Unit = {
val m1 = HashMap(1 -> "a", 2 -> "b")
val m2 = m1.withDefaultValue(0)
assertEquals("a", m2(1))
assertEquals(0, m2(3))
}
}