Explicitly specify an associated type - swift

In the following example, my 'Type' has an 'Option'. And I use them in a Field struct by insuring that they are coherent thanks to the where clause in the Generics part.
protocol Type {
associatedtype O: Option
var typeOption: O? { get }
}
protocol Option {
}
struct Field<T: Type, O: Option where T.O == O> {
let type: T
let option: O
}
It works fine. But the typeOption property is useless. In fact I put it only so the type of the Option can be inferred as with the String's extension example.
struct StringOption: Option {
}
extension String: Type {
var typeOption: StringOption? {
return nil
}
}
So my question is, can I get rid of this useless property or, in other words, can I explicitly specify the associated type?

Related

Swift: Same-Type requirement makes generic parameters equivalent?

I'm using swift 5 and try to compile the following code:
protocol BasicProtocol {
associatedtype T
var str: T {get set}
}
struct AItem<U>: BasicProtocol {
typealias T = U
var str: T
init<G: StringProtocol>(str: G) where G == T {
self.str = str
}
}
I got compilation error:
error: Test.playground:10:45: error: same-type requirement makes generic parameters 'G' and 'U' equivalent
init<G: StringProtocol>(str: G) where G == T {
^
How to make them equivalent? or I can't?
Thanks.
Update 1:
This is the problem I encountered: I want to declare struct "AItem", hoping it has a generic type "T". And this generic type will have some restrictions, such as: "T: StringProtocol". Then for some reason, I need to use an array to load these structs, and ensure that the generics of each structure can be set at will.
I learned that there is "type-erase" might can solve this. So I tried this way, but it seemed unsuccessful. The problems mentioned above have occurred.
Update 2:
struct AItem<T: StringProtocol> {
var aStr: T
}
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
Look,If you compile this code, you will get a compilation error:
error: Test.playground:5:13: error: type 'Any' does not conform to protocol 'StringProtocol'
var array: [AItem<Any>] = [AItem(aStr: "asdfasdf")]
^
If I use "var array: [AItem<String>]", I will not be able to put any other non-"String" but implemented "StringProtocol" instance in the array.
This is why I said I want "ensure that the generics of each structure can be set at will".
Update 3:
very thanks for #jweightman, now I update my question again.
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
.......
struct AItem<T = which class has Implemented "ConstraintProtocol"> {
var aPara: T
init(aPara:T) {
self.aPara = aPara
}
}
// make a array to contain them
var anArray: [AItem<Any class which Implemented "ConstraintProtocol">] = [AItem(aPara: "String"), AItem(aPara: 1234), AItem(aPara: Data("a path")), …]
// then I can use any item which in anArray. Maybe I will implement a method to judge these generics and perform the appropriate action.
for curItem in anArray {
var result = handleItem(curItem)
do something...
}
func handleItem<T: ConstraintProtocol>(item: AItem<T>) -> Any? {
if (item.T is ...) {
do someThing
return ......
} else if (item.T is ...) {
do someThing
return ...
}
return nil
}
This is my whole idea, but all of which are pseudo-code.
It seems like type erasure is the answer to your problem. The key idea to the type erasure pattern is to put your strongly typed but incompatible data (like an AItem<String> and an AItem<Data>) inside of another data structure which stores them with "less precise" types (usually Any).
A major drawback of type erasure is that you're discarding type information—if you need to recover it later on to figure out what you need to do with each element in your array, you'll need to try to cast your data to each possible type, which can be messy and brittle. For this reason, I've generally tried to avoid it where possible.
Anyways, here's an example of type erasure based on your pseudo code:
protocol ConstraintProtocol {}
extension String: ConstraintProtocol{}
extension Data: ConstraintProtocol{}
extension Int: ConstraintProtocol{}
struct AItem<T: ConstraintProtocol> {
var aPara: T
init(aPara: T) {
self.aPara = aPara
}
}
struct AnyAItem {
// By construction, this is always some kind of AItem. The loss of type
// safety here is one of the costs of the type erasure pattern.
let wrapped: Any
// Note: all the constructors always initialize `wrapped` to an `AItem`.
// Since the member variable is constant, our program is "type correct"
// even though type erasure isn't "type safe."
init<T: ConstraintProtocol>(_ wrapped: AItem<T>) {
self.wrapped = wrapped
}
init<T: ConstraintProtocol>(aPara: T) {
self.wrapped = AItem(aPara: aPara);
}
// Think about why AnyAItem cannot expose any properties of `wrapped`...
}
var anArray: [AnyAItem] = [
AnyAItem(aPara: "String"),
AnyAItem(aPara: 1234),
AnyAItem(aPara: "a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
// Note that this function is no longer generic. If you want to try to "recover"
// the type information you erased, you will have to do that somewhere. It's up
// to you where you want to do this.
func handleItem(item: AnyAItem) -> String {
if (item.wrapped is AItem<String>) {
return "String"
} else if (item.wrapped is AItem<Data>) {
return "Data"
} else if (item.wrapped is AItem<Int>) {
return "Int"
}
return "unknown"
}
An alternative to type erasure you could consider, which works well if there's a small, finite set of concrete types your generic could take on, would be to use an enum with associated values to define a "sum type". This might not be a good choice if the protocol you're interested in is from a library that you can't change. In practice, the sum type might look like this:
enum AItem {
case string(String)
case data(Data)
case int(Int)
}
var anArray: [AItem] = [
.string("String"),
.int(1234),
.data("a path".data(using: .utf8)!)
]
for curItem in anArray {
let result = handleItem(item: curItem)
print("result = \(result)")
}
func handleItem(item: AItem) -> String {
// Note that no casting is required, and we don't need an unknown case
// because we know all types that might occur at compile time!
switch item {
case .string: return "String"
case .data: return "Data"
case .int: return "Int"
}
}

Can I get the name of a type conforming to a protocol from that protocol?

I would like to know if I can find the name of a type conforming to a given protocol, from that protocol. I was thinking of protocol extension to avoid repetition in every type conforming to that protocol. I tried this:
protocol T {
var type: String { get }
}
extension T {
var type: String {
return String(describing: T.self)
}
}
struct S: T {}
let s = S()
print(s.type)
But this is showing T instead of S.
Is there any way I can do this?
Naturally it's printing T, that's what you asked for with String(describing: T.self). T is always the protocol itself.
Inside the protocol extension Self (capital 'S') is how you refer to the conforming type.
So the extension should be:
extension T {
var typeName: String {
return String(describing: Self.self)
}
}
Aside, the built-in type(of:) function already gives you the dynamic type of any object, so it's not clear that you really need to duplicate this functionality on your own.

Swift: Generic's type protocol not being recognized

Long time listener, first time caller.
I'm getting the following error:
Cannot convert value of type MyClass<Model<A>, OtherClass> to expected argument type MyClass<Protocol, OtherClass>
Despite the fact that MyClass<T> conforms to Protocol
I've attached a snippet that can be run in Playgrounds that resembles what I am actually trying to achieve.
protocol DisplayProtocol {
var value: String { get }
}
class DataBundle<T: CustomStringConvertible>: DisplayProtocol {
var data: T
var value: String {
return data.description
}
init(data: T) {
self.data = data
}
}
class Mapper<DisplayProtocol, Data> {
// ...
}
class MapperViewModel<Data> {
let mapper: Mapper<DisplayProtocol, Data>
init(mapper: Mapper<DisplayProtocol, Data>) {
self.mapper = mapper
}
}
let dataBundle = DataBundle<Int>(data: 100)
let mapper = Mapper<DataBundle<Int>, Bool>()
let viewModel = MapperViewModel<Bool>(mapper: mapper) // <- This fails w/error
Is this the expected behavior? If it is it feels like its breaking the contract of allowing me to have the DisplayProtocol as a type in Mapper.
This is caused by the fact that Swift generics are invariant in respect to their arguments. Thus MyClass<B> is not compatible with MyClass<A> even if B is compatible with A (subclass, protocol conformance, etc). So yes, unfortunately the behaviour is the expected one.
In your particular case, if you want to keep the current architecture, you might need to use protocols with associated types and type erasers.

Generic constrained type default value

Consider the following code:
protocol JSONParserType {
associatedtype Element
}
// MARK: - Entities
struct Item {}
// MARK: - Parsers
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init(innerParser: T = InnerParser()) {
self.innerParser = innerParser
}
}
struct InnerParser: JSONParserType {
typealias Element = Item
}
The OuterParser has a child parser that should be constrained to a specific type. Unfortunately providing a default value in the initializer (or in the property definition itself) does lead to the compiler throwing a "Default argument value of type 'InnerParser' cannot be converted to type 'T'".
If I remove the default value assignment and just instantiate the OuterParser providing the InnerParser explicitly, everything is fine.
let outerParser = OuterParser(innerParser: InnerParser())
My question is what's the reason that the approach providing a default value that actually meets the constraints does not work.
The problem is that the actual type of T isn't defined by the class – it's defined by the code that uses the class. It will therefore be defined before you do anything in your class (at either instance or static level). You therefore can't assign InnerParser to T, as T has already been defined to be a given type by that point, which may well not be InnerParser.
For example, let's consider that you have another parser struct:
struct AnotherParser: JSONParserType {
typealias Element = Item
}
and let's assume that your current code compiles. Now consider what would happen when you do this:
let parser = OuterParser<AnotherParser>()
You've defined the generic type to be AnotherParser – but the initialiser will try to assign InnerParser to your property (now of type AnotherParser). These types don't match, therefore it cannot possibly work.
Following the same logic, this implementation also won't work:
struct OuterParser<T: JSONParserType where T.Element == Item>: JSONParserType {
typealias Element = Item
let innerParser: T
init() {
self.innerParser = InnerParser()
}
init(innerParser: T) {
self.innerParser = innerParser
}
}
As there's no guarantee that the generic type T will be the same type as InnerParser. Sure, you can force downcast to T – but that'll just make you code crash if the types aren't compatible.
Unfortunately, there's no real clean solution to this problem. I think the best your best option is probably to create two factory methods for creating your OuterParser instance.
enum Parser {
static func createParser() -> OuterParser<InnerParser> {
return OuterParser(innerParser:InnerParser())
}
static func createParser<T>(innerParser:T) -> OuterParser<T> {
return OuterParser(innerParser:innerParser)
}
}
let innerParser = Parser.createParser() // OuterParser<InnerParser>
let anotherParser = Parser.createParser(AnotherParser()) // OuterParser<AnotherParser>
We're using an caseless enum here to avoid polluting the global namespace with extra functions.
Although this isn't very Swifty, and for that reason I would also recommend maybe rethinking your logic for how you define your parsers.
type T more like a child protocol of JSONParserType you can convert it:
init(innerParser: T = InnerParser() as! T) {
self.innerParser = innerParser
}

How to specify multiple inter-dependent associated types in swift protocols?

How do I specify multiple associated types in a protocol with added dependencies on their inner associated types? For example:
protocol CombinedType
{
typealias First: FirstType
typealias Second: SecondType
var first: First { get }
var second: Second { get }
}
protocol FirstType
{
typealias Value
var value: Value { get }
}
protocol SecondType
{
type alias Value
var value: Value { get }
}
I need to specify a limitation on the CombinedType protocol that would make it so FirstType.Value == SecondType.Value much like in a where clause in generics.
Without that limitation, the code:
func sum<Combined: CombinedType>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
produces an error Combined.FirstType.Value is not compatible with Combined.SecondType.Value.
But if I explicitly add the where clause to the function, it compiles:
func sum<Combined: CombinedType where Combined.FirstType.Value == Combined.SecondType.Value>(combined: Combined) -> Combined.FirstType.Value
{
return combined.first + combined.second
}
The problem is that this where needs to be copied everywhere, which produces a ton of boilerplate and it gets even worse when using CombinedType inside another protocol, in which case the where clause gets larger and larger.
My question is: can I somehow include that limitation in the protocol itself?