i have some rendering context protocol RenderContext
protocol RenderContext:AnyObject
i want to support some syntax resembling
let context:any RenderContext
let foo:Foo
html[.ol]
{
$0[.li] = foo | context
}
let foos:[Foo]
html[.ol] = foos | context
the first thing i tried was an extension on RenderContext
extension RenderContext
{
static func | (item:Foo, self:Self) -> FooItem?
static func | (list:[Foo], self:Self) -> FooList?
}
but that didn’t work, because you cannot implicitly open an existential RenderContext this way.
so i then rewrote the first operator as an extension of Foo, because Foo is a type we own.
extension Foo
{
static
func | (self:Self, context:some RenderContext) -> FooItem?
}
but this doesn’t generalize to [Foo], because that would have to become an extension on Array, a shared namespace.
extension [Foo]
{
static
func | (self:Self, context:some RenderContext) -> FooList?
}
this causes no problems for code completion, because it is still constrained to the type we own. however, in generated documentation this API would not be associated with Foo, or FooList even, instead it appears as an extension of Array.
i decided this was okay because this is an internal API and having disorganized documentation felt like the lesser evil. but for a public API, it would be much better for these operator functions to be associated with a more relevant type than Array. ideally, they would just be associated with RenderContext.
we could promote them all to free functions, and curate them manually. but this approach has major issues, because |(_:_:) will require hash disambiguation 100 percent of the time, and hash disambiguation 1) imposes ABI stability and 2) limits the package to a single platform.
are there good reasons why implicitly-opened existentials can’t Just Work with operator functions?