Jon Vogel
Jon Vogel

Reputation: 5634

Protocols, Structs, but no concrete type

Ok I have a protocol called Environment

protocol Environment {
  var rootURL: String {get}
}

Then two structs:

struct Production: Environment {
  var rootURL = "www.api.mybackend.com/v1"
}

struct Development: Environment {
  var rootURL = "www.api.mydevelopmentbackend.com/v1"
}

A settings object with a function that retrieves the environment:

class Settings {
  func getEnvironment<T>() -> T where T: Environment {
    let environmentRaw = self.retreiveEnvironmentFromLocalStore()
    switch environmentRaw {
    case 0:
        return Development() as! T
    case 1:
        return Production() as! T
    default:
        return Development() as! T
    }
  }

  func retreiveEnvironmentFromLocalStore() -> Int {
    //Realm, SQLLite, Core Date, I don't really care for this example. 
    //Let's just say it defaults to returning 1
  }
}

I really want to keep moving in a Protocol Oriented Programming direction but now when I call this function on a settings object and try to use the rootURL property the compiler complains it can't figure out the type. So, to better my understanding and to maybe find a solution:

1) Why does it care about the type if I am accessing a property defined by a protocol that at the very least it knows the returning type conforms to?

2) Structs don't have inheritance. Should I define a base class and forget about generics?

3) Can I make my getEnvironment function better? I don't like force casting, it seems like a code smell.

4) Am I even using generics correctly here?

EDIT 1:

To be clear, I want one function that returns a struct that I know will have this property.

Upvotes: 0

Views: 327

Answers (2)

rob mayoff
rob mayoff

Reputation: 385680

I really want to keep moving in a Protocol Oriented Programming direction

I suggest instead you try to move in a clear, understandable code direction, regardless of what the trendy orientation is.

Here's your function declaration:

func getEnvironment() -> T where T: Environment

This says that getEnvironment() will return an object of some type T, where T is deduced at compile time based on the code that calls getEnvironment().

What types could T be? It could be either Production or Development. For example, you could write:

let e: Production = Settings().getEnvironment()

This lets the compiler deduce that getEnvironment() returns a Production (which is an Environment) at this call site.

But there's a problem: getEnvironment() might try to return a Development anyway, based on the random number generator inside retreiveEnvironmentFromLocalStore. Then you'll get a crash at run time when it fails to cast Development to Production.

Why do you think getEnvironment() needs to be generic at all? Based on the code in your question, it shouldn't be.

import Foundation

protocol Environment {
    var rootURL: String {get}
}

struct Production: Environment {
    var rootURL = "www.api.mybackend.com/v1"
}

struct Development: Environment {
    var rootURL = "www.api.mydevelopmentbackend.com/v1"
}

class Settings {
    func getEnvironment() -> Environment {
        let environmentRaw = self.retreiveEnvironmentFromLocalStore()
        switch environmentRaw {
        case 1: return Production()
        default: return Development()
        }
    }

    func retreiveEnvironmentFromLocalStore() -> Int {
        return Int(arc4random())
    }
}


let settings = Settings()
let e = settings.getEnvironment()

Style note: the Swift API Design Guidelines advise us to

  • Name functions and methods according to their side-effects

    • Those without side-effects should read as noun phrases, e.g. x.distance(to: y), i.successor().

Unless the methods in Settings have important side effects, better names would be environment() and rawEnvironmentFromLocalStore().

Upvotes: 3

Sweeper
Sweeper

Reputation: 271775

Am I even using generics correctly here?

No, I don't think so. You are saying that getEnvironment will return T which can be any type that the client code specifies, as long as it implements Environment. However, the implementation of the method doesn't do this. It will only return two kinds of Environments, and which type it returns is not determined by the client code. The type returned depends on what retreiveEnvironmentFromLocalStore returns. Hence, generics is not suitable in this case.

Since the type of environment it returns is decided by the implementation of the method, instead of the client code, you should make use of polymorphism here - make it return an Environment instead:

  func getEnvironment() -> Environment {
    let environmentRaw = self.retreiveEnvironmentFromLocalStore()
    switch environmentRaw {
    case 0:
        return Development()
    case 1:
        return Production()
    default:
        return Development()
    }
  }

Upvotes: 3

Related Questions