Swift - KeyPath extension - enforce conformance to protocol - swift

ideally, I'd like to get the name of the property referenced by a KeyPath. But this seems not to be possible out-of-the-box in Swift.
So my thinking is that the KeyPath could provide this information based on protocol extension added by a developer. Then I'd like to design an API with an initializer/function that accepts a KeyPath conforming to that protocol (that adds a computed property).
So far I was only able to define the protocol and conditional conformance of the protocol. The following code compiles fine.
protocol KeyPathPropertyNameProviding {
var propertyName: String {get}
}
struct User {
var name: String
var age: Int
}
struct Person {
var name: String
var age: Int
}
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T>) {
if let property = property as? KeyPathPropertyNameProviding {
self.propertyName = property.propertyName
}
}
}
let userAge = \User.age as? KeyPathPropertyNameProviding
print(userAge?.propertyName) // "nil"
let personAge = \Person.age as? KeyPathPropertyNameProviding
print(personAge?.propertyName) // "age"
let wrapper = PropertyWrapper<Person>(property: \.age)
print(wrapper.propertyName) // "age"
But I am unable to restrict the API so that initialization parameter property has to be a KeyPath AND must conform to a certain protocol.
For example, the following would result in a compilation error but should work from my understanding (but probably I miss a key detail ;) )
struct PropertyWrapper<Model> {
var propertyName: String = ""
init<T>(property: KeyPath<Model, T> & KeyPathPropertyNameProviding) {
self.propertyName = property.propertyName // compilation error "Property 'propertyName' requires the types 'Model' and 'Person' be equivalent"
}
}
Any tips are highly appreciated!

You are misunderstanding conditional conformance. You seem to want to do this in the future:
extension KeyPath: KeyPathPropertyNameProviding where Root == Person {
var propertyName: String {
switch self {
case \Person.name: return "name"
case \Person.age: return "age"
default: return ""
}
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == User {
var propertyName: String {
...
}
}
extension KeyPath: KeyPathPropertyNameProviding where Root == AnotherType {
var propertyName: String {
...
}
}
But you can't. You are trying to specify multiple conditions to conform to the same protocol. See here for more info on why this is not in Swift.
Somehow, one part of the compiler thinks that the conformance to KeyPathPropertyNameProviding is not conditional, so KeyPath<Model, T> & KeyPathPropertyNameProviding is actually the same as KeyPath<Model, T>, because somehow KeyPath<Model, T> already "conforms" to KeyPathPropertyNameProviding as far as the compiler is concerned, it's just that the property propertyName will only be available sometimes.
If I rewrite the initialiser this way...
init<T, KeyPathType: KeyPath<Model, T> & KeyPathPropertyNameProviding>(property: KeyPathType) {
self.propertyName = property.propertyName
}
This somehow makes the error disappear and produces a warning:
Redundant conformance constraint 'KeyPathType': 'KeyPathPropertyNameProviding'

Key paths are hashable, so I recommend a dictionary instead. It's especially easy to put it together with strong typing if you're able to use a CodingKey type.
struct Person: Codable {
var name: String
var age: Int
enum CodingKey: Swift.CodingKey {
case name
case age
}
}
extension PartialKeyPath where Root == Person {
var label: String {
[ \Root.name: Root.CodingKey.name,
\Root.age: .age
].mapValues(\.stringValue)[self]!
}
}
Then use parentheses instead of the cast you demonstrated. No need for a protocol so far…
(\Person.name).label // "name"
(\Person.age).label // "age"
This will probably all be cleaner due to built-in support someday. https://forums.swift.org/t/keypaths-and-codable/13945

Related

Is there a way to simplify this 'matrix of overloads' based on argument types which are all ultimately representable by a specific type?

We're trying to create a function addQueryItem which ultimately uses a string and an optional string internally.
For more flexibility in the API, rather than use String for the argument types, we are instead using CustomStringConvertible (which String implements) so we can use anything that can be represented as a string.
Additionally, so we can pass it String-based enums, we also want it to accept RawRepresentable types where RawValue is a CustomStringConvertible itself.
However, since we're now technically accepting two different kinds of values for each parameter, we end up having to create a 'matrix of overloads'--four total--for each combination of the two types.
My first thought was to use protocol-oriented programming by extending RawRepresentable so it adheres to CustomStringConvertible if its RawValue was also a CustomStringConvertible. Then I could just pass that directly to the version which takes two CustomStringConvertible arguments and eliminate the other three. However, the compiler didn't like it because I'm trying to extend a protocol, not a concrete type.
// This doesn't work
extension RawRepresentable : CustomStringConvertible
where RawValue:CustomStringConvertible {
var description: String {
return self.rawValue
}
}
As a result of not being able to do the above, as mentioned, I have to have all four of the following:
func addQueryItem(name:CustomStringConvertible, value:CustomStringConvertible?){
if let valueAsString = value.flatMap({ String(describing:$0) }) {
queryItems.append(name: String(describing:name), value: valueAsString)
}
}
func addQueryItem<TName:RawRepresentable>(name:TName, value:CustomStringConvertible?)
where TName.RawValue:CustomStringConvertible {
addQueryItem(name: name.rawValue, value: value)
}
func addQueryItem<TValue:RawRepresentable>(name:CustomStringConvertible, value:TValue?)
where TValue.RawValue:CustomStringConvertible {
addQueryItem(name: name, value: value?.rawValue)
}
func addQueryItem<TName:RawRepresentable, TValue:RawRepresentable>(name:TName, value:TValue?)
where TName.RawValue:CustomStringConvertible,
TValue.RawValue:CustomStringConvertible
{
addQueryItem(name: name.rawValue, value: value?.rawValue)
}
So, since it doesn't look like it's possible to make RawRepresentable to adhere to CustomStringConvertible, is there any other way to solve this 'matrix-of-overloads' issue?
To expand on my comments, I believe you're fighting the Swift type system. In Swift you generally should not try to auto-convert types. Callers should explicitly conform their types when they want a feature. So to your example of an Order enum, I believe it should be implemented this way:
First, have a protocol for names and values:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
Now for string-convertible enums, it's nice to not have to implement this yourself.
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
But, for type-safety, you need to explicitly conform to the protocol. This way you don't collide with things that didn't mean to be used this way.
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
Now maybe QueryItems really has to take strings. OK.
class QueryItems {
func append(name: String, value: String) {}
}
But the thing that wraps this can be type-safe. That way Order.buy and Purchase.buy don't collide (because they can't both be passed):
class QueryBuilder<Name: QueryName, Value: QueryValue> {
var queryItems = QueryItems()
func addQueryItem(name: QueryName, value: QueryValue?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
You can use the above to make it less type-safe (using things like StringCustomConvertible and making QueryBuilder non-generic, which I do not recommend, but you can do it). But I would still strongly recommend that you have callers explicitly tag the types they plan to use this way by explicitly labelling (and nothing else) that they conform to the protocol.
To show what the less-safe version would look like:
protocol QueryName {
var queryName: String { get }
}
protocol QueryValue {
var queryValue: String { get }
}
extension QueryName where Self: RawRepresentable, Self.RawValue == String {
var queryName: String { return self.rawValue }
}
extension QueryValue where Self: RawRepresentable, Self.RawValue == String {
var queryValue: String { return self.rawValue }
}
extension QueryName where Self: CustomStringConvertible {
var queryName: String { return self.description }
}
extension QueryValue where Self: CustomStringConvertible {
var queryValue: String { return self.description }
}
class QueryItems {
func append(name: String, value: String) {}
}
class QueryBuilder {
var queryItems = QueryItems()
func addQueryItem<Name: QueryName, Value: QueryValue>(name: Name, value: Value?) {
if let value = value {
queryItems.append(name: name.queryName, value: value.queryValue)
}
}
}
enum Order: String, RawRepresentable, QueryName {
case buy
}
enum Item: String, RawRepresentable, QueryValue {
case widget
}
No, you cannot conform a protocol to another protocol via an extension. The language does not support it.

Multiple enum implementing protocols questions

I defined enums as confirming to a protocol Eventable:
protocol Eventable {
var name: String { get }
static var all: [Eventable] { get }
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
var name: String {
return self.rawValue
}
static var all: [Eventable] {
return [
MyEnum.bla,
MyEnum.blu
]
}
}
I have other enums like MyEnum also under the form:
enum Bla: String, Eventable {
}
I have two questions:
for the enums having a String data type, I would like to avoid duplicating the generation of the variable name:
var name: String
I am not sure how to write that in Swift. I tried to play around with the "where" clause but not success. How can I achieve that?
when I write my enums and conform to the protocol for that part:
static var all: [Eventable] { get }.
I would like that for the enum MyEnum, it constrains the variable to:
static var all: [MyEnum] { ... }
because for now I can put in the returned array any element being an Eventable and it's not what I need.
Amongst other things, I tried to define a generic constraint in the protocol for it, but I get the following error:
Protocol 'Eventable' can only be used as a generic constraint because
it has Self or associated type requirements
Thank you very much for the help!
For your second question, you just need to use Self:
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
Self, similar to self, just means "the current type".
The first question is a little bit harder because you can't really get all the values of an enum safely. See here for more info. The closest I got was:
extension Eventable where Self: RawRepresentable, Self.RawValue == String {
var name: String {
return self.rawValue
}
}
This means that you can omit the declaration of name in MyEnum, but not all.
For the part of the question we need to extend Eventable protocol where Self inherits RawRepresentable
protocol Eventable {
var name: String { get }
static var all: [Self] { get }
}
extension Eventable where Self: RawRepresentable {
var name: Self.RawValue {
return self.rawValue
}
}
enum MyEnum: String, Eventable {
case bla = "bla"
case blu = "blu"
static var all: [MyEnum] = [bla, blu]
}
For the second part of your question we need to configure the function to handle a generic type
I would suggest making the function generic as well here is an example
func printEnum<T: Eventable>(_ event: T) {
print(event.name)
}
and we can use it for any object confirms to Eventable
printEnum(MyEnum.bla)

What is the proper way to reference a static variable on a Swift Protocol?

Assume a protocol defined below:
protocol Identifiable {
static var identifier: String { get }
}
extension Identifiable {
static var identifier: String { return "Default Id" }
}
What is the best way to reference the static variable? The example below illustrates two ways to access the variable. What is the difference, and is the type(of:) better?
func work<I: Identifiable>(on identifiable: I) {
let identifier: String = I.identifier
print("from Protocol: \(identifier)")
let identiferFromType: String = type(of: identifiable).identifier
print("using type(of:): \(identiferFromType)")
}
struct Thing: Identifiable {
static var identifier: String { return "Thing" }
}
work(on: Thing())
In the example you show, there is no difference. Because identifier is a protocol requirement, it will be dynamically dispatched to in both cases, therefore you don't need to worry about the wrong implementation being called.
However, one difference arises when you consider the value of self inside the static computed property when classes conform to your protocol.
self in a static method/computed property is the metatype value that it's is called on. Therefore when called on I, self will be I.self – which is the static type that the compiler infers the generic placeholder I to be. When called on type(of: identifiable), self will be the dynamic metatype value for the identifiable instance.
In order to illustrate this difference, consider the following example:
protocol Identifiable {
static var identifier: String { get }
}
extension Identifiable {
static var identifier: String { return "\(self)" }
}
func work<I : Identifiable>(on identifiable: I) {
let identifier = I.identifier
print("from Protocol: \(identifier)")
let identiferFromType = type(of: identifiable).identifier
print("using type(of:): \(identiferFromType)")
}
class C : Identifiable {}
class D : C {}
let d: C = D()
// 'I' inferred to be 'C', 'type(of: d)' is 'D.self'.
work(on: d)
// from Protocol: C
// using type(of:): D
In this case, "which is better" completely depends on the behaviour you want – static or dynamic.

How to work around Swift not supporting first class meta types?

So I'm implementing the following:
A simple LanguageType protocol, which conforms to Hashable
A Translateable protocol, which should allow you to get (and set) a [String] from a dictionary, using a LanguageType as key
// MARK: - LanguageType
protocol LanguageType: Hashable {
var description: String { get }
}
extension LanguageType {
var description: String { return "\(Self.self)" }
var hashValue: Int { return "\(Self.self)".hashValue }
}
func ==<T: LanguageType, U: LanguageType>(left: T, right: U) -> Bool {
return left.description == right.description
}
// MARK: - Translateable
protocol Translateable {
var translations: [LanguageType: [String]] { get set }
}
As usual, Swift has a problem with the way the LanguageType protocol is used:
From what I've read, this has to do with Swift not supporting Existentials, which results in protocols not actually being first class types.
In the context of generics this problem can usually be solved with a type-erased wrapper.
In my case there are no generics or associated types though.
What I want to achieve is to have translations.Key to be any LanguageType, not just one generic type conforming to LanguageType.
So for example this wouldn't work:
protocol Translateable {
typealias Language: LanguageType
var translations: [Language: [String]] { get set }
}
For some reason I just can't think of a way to achieve this. I find it sounds like I need some kind of type-erased wrapper, as I want
translations.Key to be any LanguageType
I think I need to erase the exact type, which is supposed to conform to LanguageType in Translateable.
What can I do to fix this issue?
Update 1:
As just determined in this question, LanguageType actually has associated type requirements (do to it's conformance to Equatable). Therefore I will try to create a type-erased wrapper around LanguageType.
Update 2:
So I've realized, that creating a type-erased wrapper for LanguageType won't actually resolve the problem. I've created AnyLanguage:
struct AnyLanguage<T>: LanguageType {
private let _description: String
var description: String { return _description }
init<U: LanguageType>(_ language: U) { _description = language.description }
}
func ==<T, U>(left: AnyLanguage<T>, right: AnyLanguage<U>) -> Bool {
return left.description == right.description
}
If I now used it in place of LanguageType it wouldn't do much, as Translateable would still require an associated type:
protocol Translateable {
typealias T
var translations: [AnyLanguage<T>: [String]] { get set }
}
Solution:
I removed the generic from AnyLanguage:
struct AnyLanguage: LanguageType {
private(set) var description: String
init<T: LanguageType>(_ language: T) { description = language.description }
}
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
return left.description == right.description
}
protocol Translateable {
var translations: [AnyLanguage: [String]] { get set }
}
Not sure why I introduced T in Update 2, as it doesn't do anything. But this seems to work now.
You can't have protocols as a key for a Dictionary, see Swift Dictionary with Protocol Type as Key. Swift need to tie the dictionary key to a concrete type.
Seems that you're trying to achieve static polymorphism and dynamic polymorphism in the same construct (the Translateable protocol), which I'm not sure it can be achieved.
A workaround would be to declare Translateable as a generic struct:
struct Translateable<T: LanguageType> {
var translations: [T: [String]]
}
Possibly you can use an enum that conforms to LanguageType can mimic the behaviour you're looking for. In that case, you needn't explicitly include conformance to hashable in LanguageType, as enums are Hashable.
protocol LanguageType {
var description: String { get }
// ...
}
extension LanguageType {
var description: String { return "\(Self.self)" }
}
enum AnyLanguage : Int, LanguageType {
case English = 1, German, Swedish
// implement non-default description
var description : String {
return "Language: " + String(self)
}
}
protocol Translatable {
var myDict : [AnyLanguage:[String]] { get set }//= [:]
}
class MyFooWordList : Translatable {
private var myBackendDict : [AnyLanguage:[String]] = [:]
var myDict : [AnyLanguage:[String]] {
get {
return myBackendDict
}
set {
for (k, v) in newValue {
myBackendDict[k] = v
}
}
}
}
Example:
/* Example */
var myFooWordList = MyFooWordList()
myFooWordList.myDict = [.English: ["Hello", "World"]]
myFooWordList.myDict = [.German: ["Hallo", "Welt"]]
print("Words for '" + AnyLanguage.English.description + "': \(myFooWordList.myDict[.English] ?? ["<Empty>"])")
/* Words for 'Language: English': ["Hello", "World"] */
print("Words for '" + AnyLanguage.German.description + "': \(myFooWordList.myDict[.German] ?? ["<Empty>"])")
/* Words for 'Language: German': ["Hallo", "Welt"] */
print("Words for '" + AnyLanguage.Swedish.description + "': \(myFooWordList.myDict[.Swedish] ?? ["<Empty>"])")
/* Words for 'Language: Swedish': ["<Empty>"] */
Another workaround is to use an enum-like class where you can "dynamically add members" to this fictive enum
class LanguageType {
class AnyLanguage: Hashable {
let id: Int
let description: String
private init(id: Int, description: String) {
self.id = id
self.description = description
}
var hashValue: Int { return id }
}
class var ENGLISH: AnyLanguage {
class English: AnyLanguage {
}
return English(id: 1, description: "English")
}
class var GERMAN: AnyLanguage {
class German: AnyLanguage {
}
return German(id: 2, description: "German")
}
class func CUSTOM(id: Int, _ description: String) -> AnyLanguage {
return AnyLanguage(id: id, description: description)
}
}
func == (lhs: LanguageType.AnyLanguage, rhs: LanguageType.AnyLanguage) -> Bool {
return lhs.id == rhs.id
}
protocol Translatable {
var myDict : [LanguageType.AnyLanguage:[String]] { get set }//= [:]
}
class MyFooWordList : Translatable {
private var myBackendDict : [LanguageType.AnyLanguage:[String]] = [:]
var myDict : [LanguageType.AnyLanguage:[String]] {
get {
return myBackendDict
}
set {
for (k, v) in newValue {
myBackendDict[k] = v
}
}
}
}
Example usage
/* Example */
var myFooWordList = MyFooWordList()
myFooWordList.myDict = [LanguageType.ENGLISH: ["Hello", "World"]]
myFooWordList.myDict = [LanguageType.GERMAN: ["Hallo", "Welt"]]
myFooWordList.myDict = [LanguageType.CUSTOM(3, "Swedish"): ["Hej", "Varlden"]]
myFooWordList.myDict = [LanguageType.CUSTOM(4, "Finnish"): ["Hei", "Maailma"]]
print("Words for '" + LanguageType.ENGLISH.description + "': \(myFooWordList.myDict[LanguageType.ENGLISH] ?? ["<Empty>"])")
/* Words for 'English': ["Hello", "World"] */
print("Words for '" + LanguageType.GERMAN.description + "': \(myFooWordList.myDict[LanguageType.GERMAN] ?? ["<Empty>"])")
/* Words for 'Language: German': ["Hallo", "Welt"] */
print("Words for '" + LanguageType.CUSTOM(3, "Swedish").description + "': \(myFooWordList.myDict[LanguageType.CUSTOM(3, "Swedish")] ?? ["<Empty>"])")
/* Words for 'Swedish': ["Hej", "Varlden"] */
The solution seems to be a type-erased wrapper. Type-erasure fixes the problem of not being able to use protocols with associated types (PATs) as first-class citizens, by creating a wrapper type, which only exposes the properties defined by the protocol, which it wraps.
In this case, LanguageType is a PAT, due to its adoption of Equatable (which it conforms to, due to its adoption of Hashable):
protocol LanguageType: Hashable { /*...*/ }
Therefore it can not be used as a first-class type in the Translatable protocol:
protocol Translatable {
var translations: [LanguageType: [String]] { get set } // error
}
Defining an associated type for Translatable would not fix the problem, as this would constrain the LanguageType to be one specific type:
protocol Translatable {
typealias Language: LanguageType
var translations: [Language: [String]] { get set } // works
}
struct MyTranslatable<T: LanguageType>: Translatable {
var translations: [T: [String]] // `T` can only be one specific type
//...
}
As mentioned the solution is a type-erased wrapper AnyLanguage (Apple uses the same naming convention for their type-erased wrappers. For example AnySequence):
// `AnyLanguage` exposes all of the properties defined by `LanguageType`
// in this case, there's only the `description` property
struct AnyLanguage: LanguageType {
private(set) var description: String
// `AnyLanguage` can be initialized with any type conforming to `LanguageType`
init<T: LanguageType>(_ language: T) { description = language.description }
}
// needed for `AnyLanguage` to conform to `LanguageType`, as the protocol inherits for `Hashable`, which inherits from `Equatable`
func ==(left: AnyLanguage, right: AnyLanguage) -> Bool {
return left.description == right.description
}
// the use of `AnyLanguage` allows any `LanguageType` to be used as the dictionary's `Key`, as long as it is wrapped as `AnyLanguage`
protocol Translateable {
var translations: [AnyLanguage: [String]] { get set }
}
This implementation now allows the following:
struct SomethingTranslatable: Translatable {
var translations: [AnyLanguage: [String]] = [:]
}
func ==(left: SomethingTranslatable, right: SomethingTranslatable) -> Bool { /*return some `Bool`*/ }
struct English: LanguageType { }
struct German: LanguageType { }
var something = SomethingTranslatable()
something.translations[AnyLanguage(English())] = ["Hello", "World"]
let germanWords = something.translations[AnyLanguage(German())]
Different types, conforming to LanguageType, can now be used as the Key. The only syntactical difference, is the necessary initialization of an AnyLanguage:
AnyLanguage(English())

specialized extension of Dictionary

How can I declare an extension that will work only for a particular type?
I tried this:
extension Dictionary where
Key : CustomStringConvertible,
Value: CustomStringConvertible
{
func queryString() -> String {
var paramArray = Array<String>()
for (key, value) in self {
paramArray.append("\(key.description)=\(value.description)")
}
return "&".join(paramArray)
}
}
And it compiles fine. But when I try to use it
var d = Dictionary<String, String>()
var q = d.queryString() // <-- ERROR
I get the error:
Cannot invoke 'queryString' with no arguments
What is wrong here? I want to be able to call queryString on a Dictionary but only when it is Dictionary<String, String>
Any help is highly appreciated.
Edit
As #jtbandes said, String does not conform to CustomStringConvertible. CustomStringConvertible Protocol Reference suggests to use String() constructor to get a string rather than using the protocol as a constrain.
NOTE: String(instance) will work for an instance of any type, returning its description if the instance happens to be CustomStringConvertible. Using CustomStringConvertible as a generic constraint, or accessing a conforming type's description directly, is therefore discouraged.
extension Dictionary {
public func queryString() -> String {
var paramArray = Array<String>()
for (key, value) in self {
paramArray.append("\(String(key))=\(String(value))")
}
return "&".join(paramArray)
}
}
Edit2
This is my final version.
extension Dictionary {
public func queryString() -> String {
var queryItems = Array<NSURLQueryItem>()
for (key, value) in self {
queryItems.append(NSURLQueryItem(name: String(key), value: String(value)))
}
let comps = NSURLComponents();
comps.queryItems = queryItems
return comps.percentEncodedQuery!
}
}
String is not CustomStringConvertible. You can use:
extension String: CustomStringConvertible {
public var description: String { return self }
}
Or, I would recommend making your own protocol for this case:
protocol QueryStringConvertible {
var queryStringRepresentation: String { get }
}
extension String: QueryStringConvertible {
var queryStringRepresentation: String { return self /*probably should escape the string here*/ }
}
extension Dictionary where
Key : QueryStringConvertible,
Value : QueryStringConvertible ...
But in reality, I think you might want to take a look at NSURLQueryItem and NSURLComponents :)