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

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?

Related

How can I use a protocol's type in method signature?

I have a function like this:
// A generic function that can fetch values from the store given a type
// Corresponds to a table and it's rows
func fetch<T: FetchableRecord>(for theType: T.Type) -> [T] {
// ... fetch from a store using the type
// This compiles fine
}
How can I use this with a collection of types?
Ideally, if I have some conformers:
struct ModelA: FetchableRecord { }
struct ModelB: FetchableRecord { }
then I would like to be able to do:
let modelTypes: [FetchableRecord.Type] = [ModelA.self, ModelB.self]
modelTypes.forEach {
fetch(for: $0) // xxx: does not compile: "Cannot invoke 'fetch' with an argument list of type '(for: FetchableRecord.Type)'"
}
At the very least, I would like to figure why this would not be possible.
Thank you.
The reason for the error is FetchableRecord.Type is not the same as ModelA.Type or ModelB.Type. Even if both of the structs conform to FetchableRecord protocol, constraining the models (by conforming to a certain protocol) does not affect the "Types", more technically speaking:
ModelA.self == FetchableRecord.self OR ModelB.self == FetchableRecord.self is false.
In order to resolve this issue, you could implement the method's signiture as:
func fetch(for theType: FetchableRecord.Type) -> [FetchableRecord] {
// ...
}
therefore, your code:
let modelTypes: [FetchableRecord.Type] = [ModelA.self, ModelB.self]
modelTypes.forEach {
fetch(for: $0)
}
should work. At this point, you are dealing with it "Heterogeneously" instead of "Homogeneously".
Furthermore, if that makes it more sensible, note that when calling fetch method as per your implementation, it is:
the parameter type is FetchableRecord.Protocol. However, as per the above-mentioned implementation (in this answer), it is:
the parameter type is FetchableRecord.Type, which is the wanted result.

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.

Explicitly specify an associated type

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?

Define a Swift protocol which requires a specific type of sequence

Suppose for example we're talking about elements of type Int (but the question still applies to any type)
I have some functionality which needs to loop over a sequence of Ints. But I don't care if behind the scenes this sequence is implemented as an Array, or a Set or any other exotic kind of structure, the only requirement is that we can loop over them.
Swift standard library defines the protocol SequenceType as "A type that can be iterated with a for...in loop". So my instinct is to define a protocol like this:
protocol HasSequenceOfInts {
var seq : SequenceType<Int> { get }
}
But this doesn't work. SequenceType is not a generic type which can be specialized, it's a protocol. Any particular SequenceType does have a specific type of element, but it's only available as an associated type: SequenceType.Generator.Element
So the question is:
How can we define a protocol which requires a specific type of sequence?
Here's some other things I've tried and why they aren't right:
Fail 1
protocol HasSequenceOfInts {
var seq : SequenceType { get }
}
Protocol 'SequenceType' can only be used as a generic constraint
because it has Self or associated type requirements
Fail 2
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
I thought this one would work, but when I tried a concrete implementation using an Array we get
Type 'ArrayOfInts' does not conform to protocol 'HasSequenceOfInts'
This is because Array is not AnySequence (to my surprise... my expectation was that AnySequence would match any sequence of Ints)
Fail 3
protocol HasSequenceOfInts {
typealias S : SequenceType
var seq : S { get }
}
Compiles, but there's no obligation that the elements of the sequence seq have type Int
Fail 4
protocol HasSequenceOfInts {
var seq : SequenceType where S.Generator.Element == Int
}
Can't use a where clause there
So now I'm totally out of ideas. I can easily just make my protocol require an Array of Int, but then I'm restricting the implementation for no good reason, and that feels very un-swift.
Update Success
See answer from #rob-napier which explains things very well. My Fail 2 was pretty close. Using AnySequence can work, but in your conforming class you need to make sure you convert from whatever kind of sequence you're using to AnySequence. For example:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
class ArrayOfInts : HasSequenceOfInts {
var _seq : [Int] = [0,1,2]
var seq : AnySequence<Int> {
get {
return AnySequence(self._seq)
}
}
}
There are two sides to this problem:
Accepting an arbitrary sequence of ints
Returning or storing an arbitrary sequence of ints
In the first case, the answer is to use generics. For example:
func iterateOverInts<SeqInt: SequenceType where SeqInt.Generator.Element == Int>(xs: SeqInt) {
for x in xs {
print(x)
}
}
In the second case, you need a type-eraser. A type-eraser is a wrapper that hides the actual type of some underlying implementation and presents only the interface. Swift has several of them in stdlib, mostly prefixed with the word Any. In this case you want AnySequence.
func doubles(xs: [Int]) -> AnySequence<Int> {
return AnySequence( xs.lazy.map { $0 * 2 } )
}
For more on AnySequence and type-erasers in general, see A Little Respect for AnySequence.
If you need it in protocol form (usually you don't; you just need to use a generic as in iterateOverInts), the type eraser is also the tool there:
protocol HasSequenceOfInts {
var seq : AnySequence<Int> { get }
}
But seq must return AnySequence<Int>. It can't return [Int].
There is one more layer deeper you can take this, but sometimes it creates more trouble than it solves. You could define:
protocol HasSequenceOfInts {
typealias SeqInt : IntegerType
var seq: SeqInt { get }
}
But now HasSequenceOfInts has a typealias with all the limitations that implies. SeqInt could be any kind of IntegerType (not just Int), so looks just like a constrained SequenceType, and will generally need its own type eraser. So occasionally this technique is useful, but in your specific case it just gets you basically back where you started. You can't constrain SeqInt to Int here. It has to be to a protocol (of course you could invent a protocol and make Int the only conforming type, but that doesn't change much).
BTW, regarding type-erasers, as you can probably see they're very mechanical. They're just a box that forwards to something else. That suggests that in the future the compiler will be able to auto-generate these type-erasers for us. The compiler has fixed other boxing problems for us over time. For instance, you used to have to create a Box class to hold enums that had generic associated values. Now that's done semi-automatically with indirect. We could imagine a similar mechanism being added to automatically create AnySequence when it's required by the compiler. So I don't think this is a deep "Swift's design doesn't allow it." I think it's just "the Swift compiler doesn't handle it yet."
(Tested and working in Swift 4, which introduces the associatedtype constraints needed for this)
Declare your original protocol that things will conform to:
protocol HasSequenceOfInts {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
Now, you can just write
class ArrayOfInts : HasSequenceOfInts {
var seq : [Int] = [0,1,2]
}
like you always wanted.
However, if you try to make an array of type [HasSequenceOfInts], or assign it to a variable (or basically do anything with it), you'll get an error that says
Protocol 'HasSequenceOfInts' can only be used as a generic constraint because it has Self or associated type requirements
Now comes the fun part.
We will create another protocol HasSequenceOfInts_ (feel free to choose a more descriptive name) which will not have associated type requirements, and will automatically be conformed to by HasSequenceOfInts:
protocol HasSequenceOfInts: HasSequenceOfInts_ {
associatedType IntSequence : Sequence where IntSequence.Element == Int
var seq : IntSequence { get }
}
protocol HasSequenceOfInts_ {
var seq : AnySequence<Int> { get }
}
extension HasSequenceOfInts_ where Self : HasSequenceOfInts {
var seq_ : AnySequence<Int> {
return AnySequence(seq)
}
}
Note that you never need to need to explicitly conform to HasSequenceOfInts_ , because HasSequenceOfInts already conforms to it, and you get a full implementation for free from the extension.
Now, if you need to make an array or assign an instance of something conforming to this protocol to a variable, use HasSequenceOfInts_ as the type instead of HasSequenceOfInts, and access the seq_ property (note: since function overloading is allowed, if you made a function seq() instead of an instance variable, you could give it the same name and it would work):
let a: HasSequenceOfInts_ = ArrayOfInts()
a.seq_
This needs a bit more setup than the accepted answer, but means you don't have to remember to wrap your return value in AnySequence(...) in every type where you implement the protocol.
I believe you need to drop the requirement for it to only be Int's and work around it with generics:
protocol HasSequence {
typealias S : SequenceType
var seq : S { get }
}
struct A : HasSequence {
var seq = [1, 2, 3]
}
struct B : HasSequence {
var seq : Set<String> = ["a", "b", "c"]
}
func printSum<T : HasSequence where T.S.Generator.Element == Int>(t : T) {
print(t.seq.reduce(0, combine: +))
}
printSum(A())
printSum(B()) // Error: B.S.Generator.Element != Int
In Swift's current state, you can't do exactly what you want, maybe in the future though.
it is very specific example on request of Daniel Howard
1) type conforming to SequenceType protocol could be almost any sequence, even though Array or Set are both conforming to SequenceType protocol, most of their functionality comes from inheritance on CollectionType (which conforms to SequenceType)
Daniel, try this simple example in your Playground
import Foundation
public struct RandomIntGenerator: GeneratorType, SequenceType {
public func next() -> Int? {
return random()
}
public func nextValue() -> Int {
return next()!
}
public func generate() -> RandomIntGenerator {
return self
}
}
let rs = RandomIntGenerator()
for r in rs {
print(r)
}
As you can see, it conforms to SequenceType protocol and produce infinite stream of Int numbers. Before you will try to implement something, you have to answer yourself few questions
can i reuse some functionality, which is available 'for free' in standard Swift library?
am i trying to mimic some functionality which is not supported by Swift? Swift is not C++, Swift is not ObjectiveC ... and lot of constructions we used to use before Swift has no equivalent in Swift.
Define your question in such way that we can understand you requirements
are you looking for something like this?
protocol P {
typealias Type: SequenceType
var value: Type { get set }
}
extension P {
func foo() {
for v in value {
dump(v)
}
}
}
struct S<T: CollectionType>: P {
typealias Type = T
var value: Type
}
var s = S(value: [Int]())
s.value.append(1)
s.value.append(2)
s.foo()
/*
- 1
- 2
*/
let set: Set<String> = ["alfa", "beta", "gama"]
let s2 = S(value: set)
s2.foo()
/*
- beta
- alfa
- gama
*/
// !!!! WARNING !!!
// this is NOT possible
s = s2
// error: cannot assign value of type 'S<Set<String>>' to type 'S<[Int]>' (aka 'S<Array<Int>>')

Statically typed properties in Swift protocols

I'm trying to use Protocol-Oriented Pgrogramming for model layer in my application.
I've started with defining two protocols:
protocol ParseConvertible {
func toParseObject() -> PFObject?
}
protocol HealthKitInitializable {
init?(sample: HKSample)
}
And after implementing first model which conforms to both I've noticed that another model will be basically similar so I wanted to create protocol inheritance with new one:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
var value: AnyObject { get set }
}
A you can see this protocol has one additional thing which is value but I want this value to be type independent... Right now I have models which use Double but who knows what may show up in future. If I leave this with AnyObject I'm sentenced to casting everything I want to use it and if I declare it as Double there's no sense in calling this BasicModel but rather BasicDoubleModel or similar.
Do you have some hints how to achieve this? Or maybe I'm trying to solve this the wrong way?
You probably want to define a protocol with an "associated type",
this is roughly similar to generic types.
From "Associated Types" in the Swift book:
When defining a protocol, it is sometimes useful to declare one or
more associated types as part of the protocol’s definition. An
associated type gives a placeholder name (or alias) to a type that is
used as part of the protocol. The actual type to use for that
associated type is not specified until the protocol is adopted.
Associated types are specified with the typealias keyword.
In your case:
protocol BasicModel: HealthKitInitializable, ParseConvertible {
typealias ValueType
var value: ValueType { get set }
}
Then classes with different types for the value property can
conform to the protocol:
class A : BasicModel {
var value : Int
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
class B : BasicModel {
var value : Double
func toParseObject() -> PFObject? { ... }
required init?(sample: HKSample) { ... }
}
For Swift 2.2/Xcode 7.3 and later, replace typealias in the
protocol definition by associatedtype.