How to use a common protocol as a variable type

Say I have a protocol:

public protocol Printable { typealias T func Print(val:T) } 

And here is the implementation

 class Printer<T> : Printable { func Print(val: T) { println(val) } } 

My expectation was that I should use the Printable variable to print these values:

 let p:Printable = Printer<Int>() p.Print(67) 

The compiler complains about this error:

"Printable" can only be used as a general limitation, since it has its own or related requirements like "

Am I doing something wrong? Anyway, to fix it?

 **EDIT :** Adding similar code that works in C# public interface IPrintable<T> { void Print(T val); } public class Printer<T> : IPrintable<T> { public void Print(T val) { Console.WriteLine(val); } } //.... inside Main ..... IPrintable<int> p = new Printer<int>(); p.Print(67) 

EDIT 2: a real example of what I want. Please note that this will not compile, but represents what I want to achieve.

 protocol Printable { func Print() } protocol CollectionType<T where T:Printable> : SequenceType { ..... /// here goes implementation ..... } public class Collection<T where T:Printable> : CollectionType<T> { ...... } let col:CollectionType<Int> = SomeFunctiionThatReturnsIntCollection() for item in col { item.Print() } 
+75
generics ios xcode swift
Dec 31 '15 at 19:57
source share
3 answers

As Thomas points out, you can declare your variable without specifying a type at all (or you can explicitly specify it as a Printer<Int> . But here is an explanation why you cannot have a Printable type.

You cannot process protocols with related types, such as regular protocols, and declare them as autonomous variable types. To think about why, consider this scenario. Suppose you declared a protocol to store some arbitrary type, and then returned it:

 // a general protocol that allows for storing and retrieving // a specific type (as defined by a Stored typealias protocol StoringType { typealias Stored init(_ value: Stored) func getStored() -> Stored } // An implementation that stores Ints struct IntStorer: StoringType { typealias Stored = Int private let _stored: Int init(_ value: Int) { _stored = value } func getStored() -> Int { return _stored } } // An implementation that stores Strings struct StringStorer: StoringType { typealias Stored = String private let _stored: String init(_ value: String) { _stored = value } func getStored() -> String { return _stored } } let intStorer = IntStorer(5) intStorer.getStored() // returns 5 let stringStorer = StringStorer("five") stringStorer.getStored() // returns "five" 

Good, so good.

Now the main reason you will have the type of the variable is the protocol that implements the type, not the actual type, so you can assign different types of objects that all conform to this protocol for the same variable and get polymorphic behavior at runtime depending on what the object is actually.

But you cannot do this if the protocol has an associated type. How will the following code work in practice?

 // as you've seen this won't compile because // StoringType has an associated type. // randomly assign either a string or int storer to someStorer: var someStorer: StoringType = arc4random()%2 == 0 ? intStorer : stringStorer let x = someStorer.getStored() 

In the code above, what will be the type of x ? A Int ? Or a String ? In Swift, all types must be fixed at compile time. A function cannot dynamically switch from returning one type to another based on factors determined at runtime.

Instead, you can use only StoredType as a general limitation. Suppose you wanted to print some stored type. You can write a function like this:

 func printStoredValue<S: StoringType>(storer: S) { let x = storer.getStored() println(x) } printStoredValue(intStorer) printStoredValue(stringStorer) 

This is normal, because at compile time, it is as if the compiler writes two versions of printStoredValue : one for Int s and one for String s. In these two versions, x is known to have a specific type.

+77
Dec 31 '15 at 20:26
source share

There is another solution that was not mentioned in this question that uses the type erasure method. To get an abstract interface for a common protocol, create a class or structure that wraps an object or structure that conforms to the protocol. The wrapper class, usually called "Any {protocol name]," itself conforms to the protocol and implements its functions by forwarding all calls to the internal object. Try the example below on the playground:

 import Foundation public protocol Printer { typealias T func print(val:T) } struct AnyPrinter<U>: Printer { typealias T = U private let _print: U -> () init<Base: Printer where Base.T == U>(base : Base) { _print = base.print } func print(val: T) { _print(val) } } struct NSLogger<U>: Printer { typealias T = U func print(val: T) { NSLog("\(val)") } } let nsLogger = NSLogger<Int>() let printer = AnyPrinter(base: nsLogger) printer.print(5) // prints 5 

The printer type is known as AnyPrinter<Int> and can be used to abstract any possible implementation of the printer protocol. While AnyPrinter is not abstractly abstract, its implementation simply comes down to the actual implementation type and can be used to decouple implementation types from types using them.

It should be noted that AnyPrinter does not need to explicitly save the base instance. Actually, we cannot, since we cannot declare AnyPrinter have the Printer<T> property. Instead, we get a pointer to the _print function on the underlying print function. A call to base.print , without calling it, returns a function in which the base is specified as its own variable, and therefore is saved for future calls.

Another thing to keep in mind is that this solution is essentially another layer of dynamic sending, which means a slight decrease in performance. In addition, the type erase instance requires additional memory on top of the main instance. For these reasons, erasing a type is not a free abstraction.

Obviously, there is some work to configure type erasure, but it can be very useful if an abstraction of the general protocol is required. This template is found in a fast standard library with types such as AnySequence . Further reading: http://robnapier.net/erasure

BONUS:

If you decide you want to implement the same printer implementation everywhere, you can provide a convenience initializer for AnyPrinter that introduces this type.

 extension AnyPrinter { convenience init() { let nsLogger = NSLogger<T>() self.init(base: nsLogger) } } let printer = AnyPrinter<Int>() printer.print(10) //prints 10 with NSLog 

This can be a simple and harsh way of expressing dependencies for the protocols that you use in your application.

+27
Jan 04 '16 at 3:54 on
source share

Addressing your updated use case:

(btw Printable already the standard Swift protocol, so you probably want to choose a different name to avoid confusion)

To provide specific restrictions for protocol developers, you can limit the types of protocol types. Therefore, to create your protocol collection that requires items to be printable:

 // because of how how collections are structured in the Swift std lib, // you'd first need to create a PrintableGeneratorType, which would be // a constrained version of GeneratorType protocol PrintableGeneratorType: GeneratorType { // require elements to be printable: typealias Element: Printable } // then have the collection require a printable generator protocol PrintableCollectionType: CollectionType { typealias Generator: PrintableGenerator } 

Now, if you want to implement a collection that can only contain printable items:

 struct MyPrintableCollection<T: Printable>: PrintableCollectionType { typealias Generator = IndexingGenerator<T> // etc... } 

However, this is probably of little relevance, because you cannot restrict existing Swift structures like these to only those you implement.

Instead, you should create common functions that restrict their entry into collections containing printable items.

 func printCollection <C: CollectionType where C.Generator.Element: Printable> (source: C) { for x in source { x.print() } } 
+4
Jan 01
source share



All Articles