i used to never write extensions on external protocols. but i eventually came around to adopting the practice, and in hindsight this was really because of tooling limitations: for a long time on linux we did not have LSP, and it is really hard to use extensions on external protocols without LSP support.
but today we have swift on VSCode with modern sourcekit-lsp features, and it is “reasonably” stable now, so i write extensions on standard library protocols now.
with SE-0631, extensions on types like Optional
where the generic parameter is bound to a non-generic type read a lot like extensions on the wrapped type itself, so nowadays i think the readability of this pattern is excellent:
extension MyType?
{
}
extension [MyElement]
{
}
extension [MyKey: MyValue]
{
}
extension Sequence<MyElement>
{
}
the discoverability of these kinds of methods is still poor, because their documentation all gets pooled together in one giant slush pile on Optional
, Array
, Dictionary
, Sequence
, etc, but the excellent readability means it’s easy to skim a pageful of Optional
members and find the API i am looking for.
but parameterized extensions still have awful readability:
extension Optional
{
@inlinable public mutating
func revert<Instant, Value>(to rollbacks:GenericType<Instant, Value>.Rollbacks)
where Wrapped == GenericType<Instant, Value>.Head
{
if let head:GenericType<Instant, Value>.Head = self
{
self = rollbacks[head]
}
}
}
for an internal
or below method, i wouldn’t care, because the API fits more naturally as an extension on Optional
. but because the API is public
, i end up refactoring it into something like
extension GenericType.Rollbacks
{
@inlinable public
func revert(_ optional:inout GenericType<Instant, Value>.Head?)
{
if let head:GenericType<Instant, Value>.Head = optional
{
optional = self[head]
}
}
}
so that it doesn’t get lost in the pile of Optional
members. but this can be a lot of work, and increases the barrier to splitting up a large module into smaller modules.
thoughts?