Introduction
This proposal suggests adding an insertOrRemove(_:)
method to the SetAlgebra
protocol in Swift, and by extension to the Set
type. This method provides a concise and intuitive way to toggle the presence of an element in a Set
or any SetAlgebra
conformer, either inserting it if absent or removing it if present.
While the formSymmetricDifference
method can technically achieve this functionality, it is not discoverable and is primarily intended for bulk operations. Instead it seems much more common that developers must implement this functionality manually using if-else
logic or custom extensions. While the existing update(with:)
method partially addresses this need, it does not support true toggle behavior (removing an existing element). Adding this new method to the Swift Standard Library would simplify common workflows and improve code readability.
Motivation
The Set
type is a widely used data structure in Swift, often for managing unique elements such as selected items, active states, or identifiers. Toggling the presence of an element is a common operation, but Swift currently lacks a built-in and discoverable, declarative way to perform it.
Developers frequently resort to verbose or non-intuitive conditional logic:
if set.contains(element) {
set.remove(element)
} else {
set.insert(element)
}
mySet.formSymmetricDifference([1])
Or custom extensions:
extension Set {
mutating func insertOrRemove(_ element: Element) {
if contains(element) {
remove(element)
} else {
insert(element)
}
}
}
extension Set {
mutating func insertOrRemove(_ element: Element) {
mySet.formSymmetricDifference([element])
}
}
Adding insertOrRemove(_:)
to the Standard Library would:
- Eliminate the need for repetitive boilerplate code.
- Enhance the usability and expressiveness of the
Set
API. - Align with Swift's goals of clarity and conciseness.
Proposed Solution
Add an insertOrRemove(_:)
method to SetAlgebra
, which Set
conforms to, offering a straightforward API to toggle the presence of an element.
- If the element is present in the set, remove it.
- If the element is absent, insert it.
Method Definition:
extension SetAlgebra {
/// Toggles the membership of the given element in the set.
/// - Parameter element: The element to toggle.
mutating func insertOrRemove(_ element: Element) {
if contains(element) {
remove(element)
} else {
insert(element)
}
}
}
Example Usage:
Set
var selectedItems: Set<Int> = [1, 2, 3]
// Toggle presence of an element
selectedItems.insertOrRemove(2) // Removes 2
selectedItems.insertOrRemove(4) // Adds 4
print(selectedItems) // Output: [1, 3, 4]
OptionSet
struct ExampleOptions: OptionSet {
let rawValue: Int
static let option1 = ExampleOptions(rawValue: 1 << 0)
static let option2 = ExampleOptions(rawValue: 1 << 1)
}
var options: ExampleOptions = []
options.insertOrRemove(.option1) // Adds option1
options.insertOrRemove(.option1) // Removes option1
Relationship to formSymmetricDifference
The formSymmetricDifference method can achieve similar functionality by performing a symmetric difference between the set and a single-element set:
selectedItems.formSymmetricDifference([1])
However, this usage is:
- Non-discoverable:
formSymmetricDifference
is primarily associated with set algebra, making its use for toggling elements unintuitive. - Verbose: Requires wrapping the element in a single-element set, reducing clarity for a common operation.
The addition of insertOrRemove(_:)
provides a more natural and expressive alternative.
Alternate Naming
While insertOrRemove(_:)
is the most concise and intuitive name, other naming options include:
-
toggle(_:)
: A concise and intuitive name that emphasizes the action of flipping the membership state of the element. This naming is easy to remember and aligns with Swift’s preference for brevity.selectedItems.toggle(2) // Removes 2 selectedItems.toggle(4) // Adds 4
-
flipMembership(_:)
: Emphasizes the change in membership status within the set.selectedItems.flipMembership(2) // Removes 2 selectedItems.flipMembership(4) // Adds 4
-
invert(_:)
: Suggests reversing the presence of the element in the set. This name is succinct and easy to understand.selectedItems.invert(2) // Removes 2 selectedItems.invert(4) // Adds 4
-
alterPresence(_:)
: Highlights the change or alteration of the element’s status within the set.selectedItems.alterPresence(2) // Removes 2 selectedItems.alterPresence(4) // Adds 4
Detailed Design
The insertOrRemove(_:)
method will be declared as an extension of the SetAlgebra
protocol, allowing it to be used with all types conforming to SetAlgebra
, including Set
, OptionSet
, and any custom types.
This design aligns with existing SetAlgebra
methods like union, intersection, and subtracting, ensuring consistency and extensibility across the protocol.
This proposal is purely additive and introduces no breaking changes. It enhances the Set
and SetAlgebra
API by providing a new, concise operation without affecting current functionality.
Declaring on SetAlgebra: Benefits and Considerations
Benefits
- Broader Applicability
Declaring the method on SetAlgebra extends its functionality to all conforming types, including:- Set
- OptionSet
- Custom types conforming to SetAlgebra
- Consistency
Many existing methods like union, intersection, and subtracting are defined on SetAlgebra. Adding insertOrRemove(_:) aligns with this pattern and provides consistency in the API design. - Future-Proofing
Including this in SetAlgebra anticipates future types that might conform to the protocol, ensuring they automatically benefit from the method.
Considerations
-
Ambiguity of Semantics
The behavior of insertOrRemove might not always feel intuitive for non-Set types. For example, with OptionSet, the term “insert or remove” might feel less clear compared to a toggle of bitmask-style options. -
Complexity of Scope
Broader applicability increases the need for testing and design considerations across all conformers, potentially slowing down the adoption of the feature.
Alternatives Considered
-
Custom Extensions
Many developers already implement custom extensions to achieve this functionality. However, relying on custom extensions leads to inconsistent implementations across projects and misses the opportunity to standardize a common operation in the Swift language. -
Rely on formSymmetricDifference
Although formSymmetricDifference technically supports this functionality, its usage for toggling individual elements is not intuitive and deviates from its primary purpose. -
Use
update(with:)
Theupdate(with:)
method does not fulfill the requirements of a true toggle. It adds the element if not present but does not remove it if already present. -
Limit to Set
Limiting the method to Set simplifies the proposal but reduces its utility and future applicability. -
Do Nothing
The community could continue using custom solutions. However, this adds unnecessary friction and verbosity for a simple, common operation.
Conclusion
The addition of an insertOrRemove(_:)
method to Set
and SetAlgebra
represents a small but impactful improvement to Swift’s Standard Library. By providing a clear, expressive, and discoverable way to toggle the presence of an element, this method eliminates boilerplate code, improves readability, and aligns with Swift’s design principles of clarity and conciseness.
Declaring the method on SetAlgebra extends its utility to all conforming types, such as Set and OptionSet, ensuring broader applicability and consistency across the API. It also future-proofs the method for custom SetAlgebra conformers, making it a versatile and extensible addition.
The method name insertOrRemove(_:)
is both descriptive and intuitive, but alternate names such as invert(:)
or addOrRemove(_:)
could be considered during the Swift Evolution review to balance brevity and clarity.
This proposal:
- Simplifies a common operation, replacing verbose manual logic with a single, expressive method.
- Enhances discoverability by providing a dedicated API for toggling membership of an element.
- Aligns with existing SetAlgebra patterns, ensuring consistency with Swift’s Standard Library design.
By adopting this proposal, Swift will further empower developers with a small yet meaningful improvement that enhances usability, reduces boilerplate, and promotes clarity in working with Set-like types.
Thank you for considering this proposal!