Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add persistent Collection builder functions #166

Open
wants to merge 1 commit into
base: master
Choose a base branch
from
Open
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
5 changes: 5 additions & 0 deletions core/api/kotlinx-collections-immutable.api
Original file line number Diff line number Diff line change
@@ -1,4 +1,9 @@
public final class kotlinx/collections/immutable/ExtensionsKt {
public static final fun buildPersistentHashMap (Lkotlin/jvm/functions/Function1;)Lkotlinx/collections/immutable/PersistentMap;
public static final fun buildPersistentHashSet (Lkotlin/jvm/functions/Function1;)Lkotlinx/collections/immutable/PersistentSet;
public static final fun buildPersistentList (Lkotlin/jvm/functions/Function1;)Lkotlinx/collections/immutable/PersistentList;
public static final fun buildPersistentMap (Lkotlin/jvm/functions/Function1;)Lkotlinx/collections/immutable/PersistentMap;
public static final fun buildPersistentSet (Lkotlin/jvm/functions/Function1;)Lkotlinx/collections/immutable/PersistentSet;
public static final fun immutableHashMapOf ([Lkotlin/Pair;)Lkotlinx/collections/immutable/PersistentMap;
public static final fun immutableHashSetOf ([Ljava/lang/Object;)Lkotlinx/collections/immutable/PersistentSet;
public static final fun immutableListOf ()Lkotlinx/collections/immutable/PersistentList;
Expand Down
5 changes: 5 additions & 0 deletions core/api/kotlinx-collections-immutable.klib.api
Original file line number Diff line number Diff line change
Expand Up @@ -236,6 +236,8 @@ final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlinx.collections.immutab
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlinx.collections.immutable/PersistentMap<out #A, #B>).kotlinx.collections.immutable/plus(kotlin.sequences/Sequence<kotlin/Pair<#A, #B>>): kotlinx.collections.immutable/PersistentMap<#A, #B> // kotlinx.collections.immutable/plus|[email protected]<out|0:0,0:1>(kotlin.sequences.Sequence<kotlin.Pair<0:0,0:1>>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlinx.collections.immutable/PersistentMap<out #A, #B>).kotlinx.collections.immutable/plus(kotlin/Array<out kotlin/Pair<#A, #B>>): kotlinx.collections.immutable/PersistentMap<#A, #B> // kotlinx.collections.immutable/plus|[email protected]<out|0:0,0:1>(kotlin.Array<out|kotlin.Pair<0:0,0:1>>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> (kotlinx.collections.immutable/PersistentMap<out #A, #B>).kotlinx.collections.immutable/plus(kotlin/Pair<#A, #B>): kotlinx.collections.immutable/PersistentMap<#A, #B> // kotlinx.collections.immutable/plus|[email protected]<out|0:0,0:1>(kotlin.Pair<0:0,0:1>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> kotlinx.collections.immutable/buildPersistentHashMap(kotlin/Function1<kotlinx.collections.immutable/PersistentMap.Builder<#A, #B>, kotlin/Unit>): kotlinx.collections.immutable/PersistentMap<#A, #B> // kotlinx.collections.immutable/buildPersistentHashMap|buildPersistentHashMap(kotlin.Function1<kotlinx.collections.immutable.PersistentMap.Builder<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?, #B: kotlin/Any?> kotlinx.collections.immutable/buildPersistentMap(kotlin/Function1<kotlinx.collections.immutable/PersistentMap.Builder<#A, #B>, kotlin/Unit>): kotlinx.collections.immutable/PersistentMap<#A, #B> // kotlinx.collections.immutable/buildPersistentMap|buildPersistentMap(kotlin.Function1<kotlinx.collections.immutable.PersistentMap.Builder<0:0,0:1>,kotlin.Unit>){0§<kotlin.Any?>;1§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentCollection<#A>).kotlinx.collections.immutable/minus(#A): kotlinx.collections.immutable/PersistentCollection<#A> // kotlinx.collections.immutable/minus|[email protected]<0:0>(0:0){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentCollection<#A>).kotlinx.collections.immutable/plus(#A): kotlinx.collections.immutable/PersistentCollection<#A> // kotlinx.collections.immutable/plus|[email protected]<0:0>(0:0){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentList<#A>).kotlinx.collections.immutable/minus(#A): kotlinx.collections.immutable/PersistentList<#A> // kotlinx.collections.immutable/minus|[email protected]<0:0>(0:0){0§<kotlin.Any?>}[0]
Expand All @@ -244,3 +246,6 @@ final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentList
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentSet<#A>).kotlinx.collections.immutable/minus(#A): kotlinx.collections.immutable/PersistentSet<#A> // kotlinx.collections.immutable/minus|[email protected]<0:0>(0:0){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentSet<#A>).kotlinx.collections.immutable/mutate(kotlin/Function1<kotlin.collections/MutableSet<#A>, kotlin/Unit>): kotlinx.collections.immutable/PersistentSet<#A> // kotlinx.collections.immutable/mutate|[email protected]<0:0>(kotlin.Function1<kotlin.collections.MutableSet<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> (kotlinx.collections.immutable/PersistentSet<#A>).kotlinx.collections.immutable/plus(#A): kotlinx.collections.immutable/PersistentSet<#A> // kotlinx.collections.immutable/plus|[email protected]<0:0>(0:0){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> kotlinx.collections.immutable/buildPersistentHashSet(kotlin/Function1<kotlinx.collections.immutable/PersistentSet.Builder<#A>, kotlin/Unit>): kotlinx.collections.immutable/PersistentSet<#A> // kotlinx.collections.immutable/buildPersistentHashSet|buildPersistentHashSet(kotlin.Function1<kotlinx.collections.immutable.PersistentSet.Builder<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> kotlinx.collections.immutable/buildPersistentList(kotlin/Function1<kotlinx.collections.immutable/PersistentList.Builder<#A>, kotlin/Unit>): kotlinx.collections.immutable/PersistentList<#A> // kotlinx.collections.immutable/buildPersistentList|buildPersistentList(kotlin.Function1<kotlinx.collections.immutable.PersistentList.Builder<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
final inline fun <#A: kotlin/Any?> kotlinx.collections.immutable/buildPersistentSet(kotlin/Function1<kotlinx.collections.immutable/PersistentSet.Builder<#A>, kotlin/Unit>): kotlinx.collections.immutable/PersistentSet<#A> // kotlinx.collections.immutable/buildPersistentSet|buildPersistentSet(kotlin.Function1<kotlinx.collections.immutable.PersistentSet.Builder<0:0>,kotlin.Unit>){0§<kotlin.Any?>}[0]
77 changes: 77 additions & 0 deletions core/commonMain/src/extensions.kt
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,10 @@ import kotlinx.collections.immutable.implementations.persistentOrderedMap.Persis
import kotlinx.collections.immutable.implementations.persistentOrderedMap.PersistentOrderedMapBuilder
import kotlinx.collections.immutable.implementations.persistentOrderedSet.PersistentOrderedSet
import kotlinx.collections.immutable.implementations.persistentOrderedSet.PersistentOrderedSetBuilder
import kotlin.contracts.ExperimentalContracts
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
import kotlin.experimental.ExperimentalTypeInference

//@Suppress("INVISIBLE_MEMBER", "INVISIBLE_REFERENCE")
//inline fun <T> @kotlin.internal.Exact ImmutableCollection<T>.mutate(mutator: (MutableCollection<T>) -> Unit): ImmutableCollection<T> = builder().apply(mutator).build()
Expand Down Expand Up @@ -766,3 +770,76 @@ public fun <K, V> Map<K, V>.toPersistentHashMap(): PersistentMap<K, V>
= this as? PersistentHashMap
?: (this as? PersistentHashMapBuilder<K, V>)?.build()
?: PersistentHashMap.emptyOf<K, V>().putAll(this)

/**
* Builds a new [PersistentList] by populating a [PersistentList.Builder] using the given [builderAction]
* and returning an immutable list with the same elements.
*
* The list passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside the function produces an unspecified behavior.
Comment on lines +778 to +779
Copy link
Contributor

Choose a reason for hiding this comment

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

Builders in kotlinx.collections.immutable are capable of constructing a new persistent collection multiple times. However, I'm struggling to identify a compelling use case for retaining a reference to the builder and utilizing it outside the builderAction. If we constraint the builder to be valid only within the function, could using a MutableList as the receiver be a more performance-efficient choice?

Copy link
Contributor

Choose a reason for hiding this comment

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

cc @ilya-g, WDYT?

*/
@OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
public inline fun <T> buildPersistentList(@BuilderInference builderAction: PersistentList.Builder<T>.() -> Unit): PersistentList<T> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return persistentListOf<T>().builder().apply(builderAction).build()
}
Comment on lines +781 to +785
Copy link
Contributor Author

@Goooler Goooler Feb 27, 2024

Choose a reason for hiding this comment

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

  1. Annotate @BuilderInference for better generic type support.
  2. Add InvocationKind.EXACTLY_ONCE contract to indicate the function parameter will be invoked exactly one time.

Ref https://github.com/JetBrains/kotlin/blob/3ff2bc403cb64cee273833d4dbbaf6d8c928edbc/libraries/stdlib/src/kotlin/collections/Collections.kt#L180-L187


/**
* Builds a new [PersistentSet] by populating a [PersistentSet.Builder] using the given [builderAction]
* and returning an immutable set with the same elements.
*
* The set passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside the function produces an unspecified behavior.
*
* Elements of the set are iterated in the order they were added by the [builderAction].
*/
@OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
public inline fun <T> buildPersistentSet(@BuilderInference builderAction: PersistentSet.Builder<T>.() -> Unit): PersistentSet<T> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return persistentSetOf<T>().builder().apply(builderAction).build()
}

/**
* Builds a new [PersistentSet] by populating a [PersistentSet.Builder] using the given [builderAction]
* and returning an immutable set with the same elements.
*
* The set passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside the function produces an unspecified behavior.
*
* Order of the elements in the returned set is unspecified.
*/
@OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
public inline fun <T> buildPersistentHashSet(@BuilderInference builderAction: PersistentSet.Builder<T>.() -> Unit): PersistentSet<T> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return persistentHashSetOf<T>().builder().apply(builderAction).build()
}

/**
* Builds a new [PersistentMap] by populating a [PersistentMap.Builder] using the given [builderAction]
* and returning an immutable map with the same key-value pairs.
*
* The map passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside the function produces an unspecified behavior.
*
* Entries of the map are iterated in the order they were added by the [builderAction].
*/
@OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
public inline fun <K, V> buildPersistentMap(@BuilderInference builderAction: PersistentMap.Builder<K, V>.() -> Unit): PersistentMap<K, V> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return persistentMapOf<K, V>().builder().apply(builderAction).build()
}

/**
* Builds a new [PersistentMap] by populating a [PersistentMap.Builder] using the given [builderAction]
* and returning an immutable map with the same key-value pairs.
*
* The map passed as a receiver to the [builderAction] is valid only inside that function.
* Using it outside the function produces an unspecified behavior.
*
* Order of the entries in the returned map is unspecified.
*/
@OptIn(ExperimentalTypeInference::class, ExperimentalContracts::class)
public inline fun <K, V> buildPersistentHashMap(@BuilderInference builderAction: PersistentMap.Builder<K, V>.() -> Unit): PersistentMap<K, V> {
contract { callsInPlace(builderAction, InvocationKind.EXACTLY_ONCE) }
return persistentHashMapOf<K, V>().builder().apply(builderAction).build()
}
10 changes: 10 additions & 0 deletions core/commonTest/src/contract/list/ImmutableListTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,16 @@ class ImmutableListTest {
}
}

@Test fun buildPersistentList() {
val expected = persistentListOf(1, 2, 3, 4, 5, 6)
val actual = buildPersistentList {
for (i in 1..6) {
add(i)
}
}
assertEquals(expected, actual)
}

@Test fun subListOfBuilder() {
val list = "abcxaxyz12".toImmutableList().toPersistentList()
val builder = list.builder()
Expand Down
30 changes: 30 additions & 0 deletions core/commonTest/src/contract/map/ImmutableMapTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -78,6 +78,21 @@ class ImmutableHashMapTest : ImmutableMapTest() {
compareMaps(expected, builder1.build())
}

@Test fun buildPersistentHashMap() {
val expected = mutableMapOf<String, Int>()
val actual = buildPersistentHashMap {
for (i in 300..400) {
put("$i", i)
expected["$i"] = i
}
for (i in 0..200) {
put("$i", i)
expected["$i"] = i
}
}
compareMapsUnordered(expected, actual)
}

@Test fun regressionGithubIssue109() {
// https://github.com/Kotlin/kotlinx.collections.immutable/issues/109
val map0 = immutableMapOf<Int, Int>().put(0, 0).put(1, 1).put(32, 32)
Expand Down Expand Up @@ -139,6 +154,21 @@ class ImmutableOrderedMapTest : ImmutableMapTest() {
changing.add("break iteration")
assertFailsWith<ConcurrentModificationException> { builder.filter { it.key === changing } }
}

@Test fun buildPersistentMap() {
val expected = mutableMapOf<String, Int>()
val actual = buildPersistentMap {
for (i in 300..400) {
put("$i", i)
expected["$i"] = i
}
for (i in 0..200) {
put("$i", i)
expected["$i"] = i
}
}
compareMaps(expected, actual)
}
}

abstract class ImmutableMapTest {
Expand Down
22 changes: 22 additions & 0 deletions core/commonTest/src/contract/set/ImmutableSetTest.kt
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,17 @@ class ImmutableHashSetTest : ImmutableSetTestBase() {
compareSets(immutableSetOf<Int>(), left - right)
}
}

@Test fun buildPersistentHashSet() {
val expected = mutableSetOf<Int>()
val actual = buildPersistentHashSet {
for (i in 0..2000) {
add(i)
expected.add(i)
}
}
compareSetsUnordered(expected, actual)
}
}

class ImmutableOrderedSetTest : ImmutableSetTestBase() {
Expand All @@ -221,6 +232,17 @@ class ImmutableOrderedSetTest : ImmutableSetTestBase() {
changing.add("break iteration")
assertFailsWith<ConcurrentModificationException> { builder.filter { it === changing } }
}

@Test fun buildPersistentSet() {
val expected = mutableSetOf<Int>()
val actual = buildPersistentSet {
for (i in 0..2000) {
add(i)
expected.add(i)
}
}
compareSets(expected, actual)
}
}

abstract class ImmutableSetTestBase {
Expand Down