I'm trying to model a generic transformation. There should be a protocol requirement, and that requirement accepts a generic collection and returns some Collection of transformed contents.
For instance, the transformation may return a LazyMapCollection
:
func transform<Source>(
_: Source
) -> LazyMapCollection<Source, UInt8> where Source: Collection, Source.Element == UInt8 {
// ...
}
My understanding is that this is a sort of "higher-kinded type" - Source
is used both by input type and output type. Indeed, it is impossible to express this today in Swift.
protocol Transformer {
associatedtype TransformedData: Collection where EncodedData.Element == UInt8
func transform<Source>(_: Source) -> TransformedData where Source: Collection, Source.Element == UInt8
}
struct MyTransformer {
// 'TransformedData' would be inferred as LazyMapCollection<Source, UInt8>
// from the function signature, but that clearly doesn't make sense here
// because we don't know what 'Source' is.
typealias TransformedData = ???
func transform<Source>(
_: Source
) -> LazyMapCollection<Source, UInt8> where Source: Collection, Source.Element == UInt8 {
// ...
}
}
This is very limiting. This function cannot return any generic wrappers at all - it will have to copy data from all Source
types to a single type (e.g. an Array
), or erase the LazyMapCollection
in an existential. In summary, it must ensure that Source
no longer appears on both sides of the function signature:
struct MyTransformer {
typealias TransformedData = AnyCollection<UInt8>
// ^^^^^^^^^^^^^^^^^^^^
// no longer mentions 'Source'
func transform<Source>(
_: Source
) -> AnyCollection<UInt8> where Source: Collection, Source.Element == UInt8 {
// ^^^^^^^^^^^^^^^^^^^^ - no longer mentions 'Source'
}
}
But there is, in theory, another option. Why couldn't we use an opaque type? This would totally work as a free function:
func transform(_: some Collection<UInt8>) -> some Collection<UInt8>
And it does what we want - it breaks the type dependency between the function's input and output, but in a way that allows the implementation to avoid copying or existential boxing. However, this appears to be banned in protocol requirements:
protocol Transformer {
func transform(_: some Collection<UInt8>) -> some Collection<UInt8>
// error: 'some' type cannot be the return type of a protocol requirement;
// did you mean to add an associated type?
}
This limitation is mentioned in SE-0244, but I don't find the reasoning to be satisfactory (emphasis added):
More fundamentally, opaque result types cannot be used in the requirements of a protocol:
protocol Q { func f() -> some P // error: cannot use opaque result type within a protocol }
Associated types provide a better way to model the same problem, and the requirements can then be satisfied by a function that produces an opaque result type. (There might be an interesting shorthand feature here, where using
some
in a protocol requirement implicitly introduces an associated type, but we leave that for future language design to explore.)
I would like to suggest that associated types cannot always be used in place of opaque types, and that there are valuable use-cases for opaque result types in protocol requirements beyond simply being shorthands for associated types.
Again, the function I am trying to express works already as a free function, so I don't see any reason why it could not also be a protocol requirement. Is there some implementation reason why it cannot be supported?
If possible, I would like us to lift this restriction. There are interesting models which we cannot express today.