How to use generic default parameters - swift

This is my code:
class Person {
init<T: RawRepresentable>(raw: T = Child.johnDoe) {}
}
enum Child: String {
case johnDoe
}
It doesn't compile. The error is:
Default argument value of type 'Child' cannot be converted to type 'T'
Why can't it be converted? According to the docs, Child.someEnum is RawRepresentable:
Enumerations with Raw Values For any enumeration with a string,
integer, or floating-point raw type, the Swift compiler automatically
adds RawRepresentable conformance. When defining your own custom
enumeration, you give it a raw type by specifying the raw type as the
first item in the enumeration’s type inheritance list.
This also compiles:
class Person {
static func accept<T: RawRepresentable>(raw: T) where T.RawValue == String {}
}
enum Child: String {
case johnDoe
}
Person.accept(raw: Child.johnDoe)
Why doesn't it work as a default parameter?
Use case: I want to accept any RawPresentable value, so I can extract the rawValue from it. I want to provide a default value (always "") (I just create a struct with rawValue = ""). I do not want to create multiple initializers, since I got some subclasses and that would get a mess. The best for me is just to provide a default RawRepresentable object.
When I add a cast:
init(ty: T = (Child.johnDoe as! T)) where T.RawValue == String {
}
Or make it nillable:
(ty: T? = nil)
It compiles. But now I can not call:
let x = Person()
It gives the error:
Generic parameter 'T' could not be inferred

This is certainly possible. However, you have to use your own protocol and add the default value to that protocol:
protocol MyRawRepresentable: RawRepresentable {
static var defaultValue: Self { get }
}
class Person {
init<T: MyRawRepresentable>(raw: T = T.defaultValue) {}
}
enum Child: String, MyRawRepresentable {
case johnDoe
static let defaultValue: Child = .johnDoe
}
There is another issue though. How will you specify the generic type if you use the default parameter value and all you will have will be just Person.init()?
The only solution I see is to also specify a default generic type which means you actually want:
class Person {
init<T: RawRepresentable>(raw: T) {
}
convenience init() {
self.init(raw: Child.johnDoe)
}
}
Unless you actually want to make Person itself a generic class because then you could just use
Person<Child>.init()

Related

avoid implicit enum raw value conversion

I am attempting to avoid the implicit conversion of an enum into a string in a case like this:
enum Animal {
case cat
}
func getTag(forAnimal animal: Animal) -> String {
// fails with "Cannot convert value of type 'Animal' to specified type 'String' which is good!!!"
// return animal
// does return "cat" which I don't want developers to be able to do!!!
return "\(animal)"
}
print(getTag(forAnimal: .cat)) // prints "cat"
The 2nd return works because print calls .description on the object.
Swift apparently adopts CustomStringConvertible for enums automatically and returns the case name as a String. Edit: Apparently , print does not call .description.
I thought about conforming to the protocol and alert developers (with something that throws an error perhaps).
But what I am wanting to do really is to have the enum NOT conform to said protocol, or have a compile-time error. Is this possible?
You can extend StringInterpolation and implement an appendInterpolation method for that specific type. This way you can simply determine how your enumeration would interpolate its value. If you don't want any string to be generated you just need to create a dummy method:
enum Animal {
case cat
}
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Animal) { }
}
Testing:
"\(Animal.cat)" // "" it results in an empty string
If you want a compile-time error you can add a deprecated availability warning:
#available(swift, deprecated: 2)
extension String.StringInterpolation {
mutating func appendInterpolation(_ value: Animal) { }
}

Make initializer generic without type in function signature

I have broken down a more complex matter to this class. This way it doesn’t make sense, but it is easier to talk about:
class GenericClass<Type: Any> {
var object: Type?
enum DataType {
case string, integer
}
init(dataType: DataType) {
switch dataType {
case .string:
object = "string" // Cannot assign value of type 'String' to type 'Type'
case .integer:
object = 1 // Cannot assign value of type 'Int' to type 'Type'
default:
object = nil
}
}
}
How can I make this initializer infer the type Type when there is no reference in the function signature?
I asked a related question before (probably with to much cluttered detail): Make Generic Class Codable
Not sure if this answers your question but this is how you are supposed to create an initializer for a generic structure/class without a specific type in the function signature (generic initializer):
class GenericClass<T> {
let object: T
init(_ object: T) {
self.object = object
}
}
let stringClass = GenericClass("string")
print(stringClass.object)
let intClass = GenericClass(1)
print(intClass.object)
This will print
string1
The actual goal/benefit of generics is to get rid of type checks at runtime.
Referring and in addition to Leo's answer if you want to constrain the types to String and Int do it at compile time
protocol GenericClassCompatible {}
extension String : GenericClassCompatible {}
extension Int : GenericClassCompatible {}
class GenericClass<T : GenericClassCompatible> {
let object: T
init(_ object: T) {
self.object = object
}
}
If the type in the init method is not String or Int you'll get an error at compile time.

Implement generic protocol method with but use generic for whole class

I'm am trying to implement a protocol method that has a generic argument, but then use the generic type for my entire class instead of just on the method, something like this
protocol FirstProtocol {
}
protocol SecondProtocol {
func foo<T: FirstProtocol>(argument: T)
}
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<T>(argument: T) {
value = argument // ERROR: Cannot assign value of type 'T' to type 'T?'
}
}
So the swift compiler accepts that foo<T>(argument:T) matches the method of SecondProtocol, if I comment out the error line it compiles fine, but it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
The type of argument and value are indeed different types. The T generic parameter in foo is just an identifier, and I can change it to anything else:
class MyType<T: FirstProtocol>: SecondProtocol {
var value: T? = nil
func foo<AnythingElse>(argument: AnythingElse) {
// MyType still conforms to SecondProtocol
}
}
The T in foo is a brand new generic parameter, different from the T in MyType. They just so happens to have the same name.
Note that when you declare a generic method, it's the caller that decides what the generic type is, not the generic method. What foo is trying to say here is "I want the T in foo to be the same type as the T in MyType", but it can't say that about its own generic parameters!
One way to fix it is to make SecondProtocol have an associated type:
protocol SecondProtocol {
// name this properly!
associatedtype SomeType: FirstProtocol
func foo(argument: SomeType)
}
class MyType<T: FirstProtocol>: SecondProtocol {
typealias SomeType = T // here is where it says "I want 'SomeType' to be the same type as 'T'!"
var value: T? = nil
func foo(argument: T) {
value = argument
}
}
it will not let me assign argument to value even though value and argument should be the same type, the compiler complains as if they are different types.
think about this case:
class A: FirstProtocol {
}
class B: FirstProtocol {
}
class A and B is the acceptable generic type for func foo(argument: T){}, but can you assign an instance of class A to class B?
class MyType<T: FirstProtocol>: SecondProtocol
remove ": FirstProtocol"should work, or use a base class to replace FirstProtocol

Is there a Swift type for a string-based enum?

I have a function that takes a String tag argument:
func findFooByTag(_ tag: String) -> Foo
Now I would like to make the code shorter and safer by introducing an enum for the valid tag values:
enum Tags: String {
case one
case two
case three
}
But I still have to call the function with a String:
let foo = findFooByTag(Tags.one.rawValue)
Is there a way to say “findFooByTag takes any string-based enum”? I have found this:
func findFooByTag<T>(_ tag: T) -> Foo where T: RawRepresentable, T.RawValue == String
But that’s quite a mouthful. Is it at least possible to sweep that under the rug with a type alias somehow?
What you have found looks awesome, but still I would suggest something like the following:
protocol Taggable {
var raw: String { get }
}
extension String: Taggable {
var raw: String { return self }
}
enum Tag: String, Taggable {
var raw: String {
return self.rawValue
}
case one = "aa"
case two = "bb"
case three = "cc"
}
func findByTag(_ tag: Taggable) {
if tag.raw == "aa" { ... }
// do something...
}
findByTag(Tag.one) // works
findByTag("abc") // also works
As there is nothing in common between enum's having a String RawValue, there is no common type for these or no protocol to which all would conform.
However, Swift 4 introduces type constraints on associated types using where clauses as described in SE-0142. Using this new capability, you can define a protocol with an associated type the type constraints describing an enum with a String rawValue, then you only need to make your Tags enum conform to this protocol and you won't need the type constraint in your function definition anymore.
class Foo {}
protocol StringEnum {
associatedtype EnumType: RawRepresentable = Self where EnumType.RawValue == String
static func findFooByTag<EnumType>(_ tag: EnumType) -> Foo
}
extension StringEnum where EnumType == Self {
static func findFooByTag<EnumType>(_ tag: EnumType) -> Foo {
return Foo()
}
}
enum Tags: String, StringEnum {
case one
case two
case three
}
let foo = Tags.findFooByTag(Tags.one)
This is implementation of course could be improved, but this is just an example showing how you can use a where clause to implement the type constraint using a protocol and its associatedType.
Due to the default implementation fo the findFooByTag func in the protocol extension, you don't need to implement the function for all of your custom enum types having a String rawValue, you only need to declare them as conforming to the StringEnum protocol.
If you don't have Xcode9 installed, you can play around with this code in the IBM Swift Sandbox using this link.
Maybe you can try do that with CustomStringConvertible?
enum Tags: String, CustomStringConvertible {
case one
case two
case three
var description: String {
return self.rawValue
}
}
func findFooByTag<T>(_ tag: T) -> Foo where T: CustomStringConvertible
looks more better
or just
func findFooByTag<T>(_ tag: CustomStringConvertible) -> Foo
For this purpose you can use any kind of wrapper object. For example:
enum TypeNotSpecifiedTag {
case one
}
enum StringTag: String {
case one
}
enum IntTag: Int {
case one = 1
}
enum Wrapper<T>: RawRepresentable {
typealias RawValue = T
case value(T)
init?(rawValue: RawValue) {
self = .value(rawValue)
}
var rawValue: RawValue {
switch self {
case let .value(item):
return item
}
}
}
print(Wrapper.value(TypeNotSpecifiedTag.one).rawValue) // "one"
print(Wrapper.value(StringTag.one.rawValue).rawValue) // "one"
print(Wrapper.value(IntTag.one.rawValue).rawValue) // 1
Notice, that according to documentation about RawValue, you doesn't always need to specify RawValue, that's why first example also compile.

Calling function with generic protocol as return type

The following example should create concrete objects for abstract protocols.
class ClassA<T: FloatingPointType> {}
protocol ProtocolA
{
typealias T: FloatingPointType
var value : ClassA<T>? { get set }
}
class ClassImplProtocolA<T: FloatingPointType> : ProtocolA
{
var value : ClassA<T>? {
get {
return nil
}
set {
}
}
}
class Factory
{
class func createProtocol<PA: ProtocolA where PA.T: FloatingPointType>() -> PA
{
let concreteObject = ClassImplProtocolA<PA.T>()
let abstractObject = concreteObject as? PA
return abstractObject!
}
}
Now two questions:
Is it possible to avoid the as? PA?
and how does the syntax look like to call Factory.createProtocol<...>()?
Or how do I work around this? All I want is an object of the abstract type PrtocolA<FloatingPointType> without exposing ClassImplProtocolA.
First you cannot avoid as? PA since the return Type is determined by the type inference like this:
let prot: ClassImplProtocolA<Double> = Factory.createProtocol()
let prot = Factory.createProtocol() as ClassImplProtocolA<Double>
Another issue in this case is that you cannot use FloatingPointType as generic type (has Self requirements) and you have to use one which conforms to this protocol (like Double and Float).
In Swift's standard library they use AnySequence, AnyGenerator,... (generic structs) in order to have an abstract type of a protocol which has Self requirements. Unfortunately you cannot make a type AnyProtocolA<FloatingPointType> because the generic type itself has Self requirements.