Skip to content

Distinguish between ordered and unordered sets and maps (urgent; can only be implemented before beta releases) #197

@Laxystem

Description

@Laxystem

The current implementation of sets and maps being 'ordered by default' is unintuitive as it isn't actually 'by default'; One would expect a persistent set to count as a persistent set, regardless of its order-ness.

val ordered = persistentSetOf("")
val unordered = persistentHashSetOf("")

check(ordered === ordered.toPersistentSet()) // true
check(ordered === ordered.toPersistentHashSet()) // false
check(unordered === unordered.toPersistentSet()) // false, what
check(unordered === unordered.toPersistentHashSet()) // true

Additionally, why do ordered sets even exist? Isn't the whole point of sets that they're unordered, unlike lists? Regardless, why are they default? Why are ordered maps the default too, for that matter? They're slower, and orderness isn't a part of maps' and sets' contracts; sure, it is a nice option sometimes, but then you could just explicitly use ordered variants; but for most use cases, wouldn't it make more sense to prioritize speed over a feature that isn't a part of the contract?

My solution

I'm assuming in both that the soon-to-be released beta will break backwards compatibility completely, as #185 talks mainly about public API changes. An educated guess tells me this is because this library is being merged into the stdlib, which means we can change the functionality of existing methods.

  1. Rename the regular persistent builders to orderedPersistent (toOrderedPersistentSet, orderedPersistentMapOf(), etc.; or persistentOrdered, doesn't really matter).
  2. Re-add the persistent builders (for both set and map; this is just an example), except for a tiny difference in implementation; Instead of:
    public fun <T> Iterable<T>.toPersistentSet(): PersistentSet<T> =
            this as? PersistentOrderedSet<T>
            ?: (this as? PersistentOrderedSetBuilder)?.build()
            ?: PersistentOrderedSet.emptyOf<T>() + this
    Have:
    public fun <T> Iterable<T>.toPersistentSet(): PersistentSet<T> =
            this as? PersistentSet<T> // changed here
            ?: (this as? PersistentSetBuilder)?.build()
            ?: PersistentOrderedSet.emptyOf<T>() + this // imo should be changed to hash but doesn't really matter
  3. Keep the hash builders as-is.

TL;DR of the solution:

val ordered = persistentOrderedSetOf("")
val unordered = persistentHashSetOf("")

check(ordered === ordered.toPersistentOrderedSet()) // true
check(ordered === ordered.toPersistentHashSet()) // false
check(unordered === unordered.toPersistentOrderedSet()) // false, ok this time
check(unordered === unordered.toPersistentHashSet()) // true

// and also
check(ordered === ordered.toPersistentSet()) // true
check(unordered === unordered.toPersistentSet()) // true

As for "but what about other implementations?" --

  1. Make all immutable interfaces sealed to prevent implementations that allow mutations #147
  2. You could add PersistentOrdered and PersistentHash (or immutable versions of them) interfaces; but it's impossible this symmetric with mutable collections without hacking the type system even further, because of Java.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions