Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use generic protocol as a variable type

Let's 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 must be able to use Printable variable to print values like this :

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

Compiler is complaining with this error :

"protocol 'Printable' can only be used as a generic constraint because it has Self or associated type requirements"

Am I doing something wrong ? Anyway to fix this ?

**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: Real world example of what I want. Note that this will not compile, but presents 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() } 
like image 771
Tamerlane Avatar asked Dec 31 '14 19:12

Tamerlane


People also ask

How do you define a variable in protocol?

First things first, variables inside a protocol must be declared as a variable and not a constant( let isn't allowed). Properties inside a protocol must contain the keyword var . We need to explicitly specify the type of property( {get} or {get set} ).

How do you define a generic variable in Swift?

Generics allow you to declare a variable which, on execution, may be assigned to a set of types defined by us. In Swift, an array can hold data of any type. If we need an array of integers, strings, or floats, we can create one with the Swift standard library.

What can protocols use to make them generic like?

Making a Protocol Generic. There are two ways to create a generic protocol - either by defining an abstract associatedtype or the use of Self (with a capital S). The use of Self or associatedtype is what we like to call "associated types". This is because they are only associated with the protocol they are defined in.

Can only be used as a generic constraint?

Protocol 'SomeProtocol' can only be used as a generic constraint because it has Self or associated type requirements. Code that uses a protocol that relies on associated types pays the price. Such code must be written using generic types. Generic types are also placeholders.


1 Answers

As Thomas points out, you can declare your variable by not giving a type at all (or you could explicitly give it as type Printer<Int>. But here's an explanation of why you can't have a type of the Printable protocol.

You can't treat protocols with associated types like regular protocols and declare them as standalone variable types. To think about why, consider this scenario. Suppose you declared a protocol for storing some arbitrary type and then fetching it back:

// 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" 

OK, so far so good.

Now, the main reason you would have a type of a variable be a protocol a type implements, rather than the actual type, is so that you can assign different kinds of object that all conform to that protocol to the same variable, and get polymorphic behavior at runtime depending on what the object actually is.

But you can't do this if the protocol has an associated type. How would 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 above code, what would the type of x be? An Int? Or a String? In Swift, all types must be fixed at compile time. A function cannot dynamically shift from returning one type to another based on factors determined at runtime.

Instead, you can only use StoredType as a generic constraint. Suppose you wanted to print out any kind of stored type. You could write a function like this:

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

This is OK, because at compile time, it's as if the compiler writes out two versions of printStoredValue: one for Ints, and one for Strings. Within those two versions, x is known to be of a specific type.

like image 61
Airspeed Velocity Avatar answered Sep 17 '22 15:09

Airspeed Velocity