Swift array of generic closures? - ios

Is it possible? The error Only syntatic function types can be generic suggests it isn't.
Valid Code
func test<T:Equatable>(function: (T) -> T){
var myArray:Array<T -> T> = [function];
}
Now I want to make a property with the same type as myArray. I feel like I should be able to do this somehow.
Doesn't work
var myArray:<T:Equatable>(T -> T)[]
Any ideas?

Even this:
func test<T:Equatable>(function: (T) -> T){
var myArray:Array<T -> T> = function;
}
Shouldn't be valid. You are assigning a T->T to a Array<T->T>. It should be at least:
var myArray:Array<T -> T> = [function];
This would work:
class MyClass<T> {
var myArray:(T->T)[] = []
}
Since you now have a generic MyClass class, to initialise, you need to tell what type T is, like so:
var x = MyClass<String>()

Related

Why can't I use a Color as a ShapeStyle? [duplicate]

I have a generic class that I want to be able to use with a default type. Right now I can initialize it with any type, but I have to be explicit.
//Initialize with a type
MyManager<MyCustomerObject>()
// Initialize with NSObject (what I want to be my default type)
MyManager<NSObject>()
// This doesn't work, but I want this kind of functionality
class MyManager<T = NSObject> {}
// So I can create my manager like so and it inserts the default type as NSObject
MyManager() //Or MyManager<>()
Is this possible in Swift?
There's no support for default generic arguments, but you can fake it by defining the default init() on a type-constrained extension, in which case the compiler will be smart enough to use that type. E.g.:
class MyManager<T> {
let instance: T
init(instance: T) {
self.instance = instance
}
}
extension MyManager where T == NSObject {
convenience init() {
self.init(instance: NSObject())
}
}
And now you can initialize the type with no argument and it will default to MyManager<NSObject>:
let mm1 = MyManager(instance: "Foo") // MyManager<String>
let mm2 = MyManager(instance: 1) // MyManager<Int>
let mm3 = MyManager() // MyManager<NSObject>
SwiftUI uses this technique quite a lot.
No, this currently isn't possible – although it is a part of the Generics Manifesto, so might be something that the Swift team will consider for a future version of the language.
Default generic arguments
Generic parameters could be given the ability to provide default
arguments, which would be used in cases where the type argument is not
specified and type inference could not determine the type argument.
For example:
public final class Promise<Value, Reason=Error> { ... }
func getRandomPromise() -> Promise<Int, Error> { ... }
var p1: Promise<Int> = ...
var p2: Promise<Int, Error> = p1 // okay: p1 and p2 have the same type Promise<Int, Error>
var p3: Promise = getRandomPromise() // p3 has type Promise<Int, Error> due to type inference
In the meantime however, a somewhat unsatisfactory compromise would be the use of a typealias:
class MyManager<T> {}
typealias MyManagerDefault = MyManager<NSObject>
let defaultManager = MyManagerDefault()
Not nearly as slick as just being able to say MyManager(), but it does show up next to MyManager in auto-complete, which is pretty handy.
If T is always a NSObject subclass, you can use a generic constraint in Swift 5.3:
class MyManager<T: NSObject> {
let t = T()
}
class MyCustomerObject: NSObject { }
let a = MyManager()
let b = MyManager<MyCustomerObject>()

AssociatedType in a protocol with typealias

Lets say i have a protocol:
protocol Router {
associatedtype Answer
typealias AnswerCallback = (Answer) -> Void
}
At some point I want to store a variable of type AnswerCallback
var answerCallback: Router.AnswerCallback ...
But I need to specify which concrete type i'm using since i get this error:
Type alias 'AnswerCallback' can only be used with a concrete type or
generic parameter base
How I can specify a type like Router.AnswerCallback ... where String is "Answer" type?
Instead the only way to work is to use var answerCallback: ((String) -> Void)
You need a class/struct that conforms to Router, which has an Answer of String:
class StringRouter : Router {
typealias Answer = String
}
let callback: StringRouter.AnswerCallback? = nil
If all you want is such a type alias, you don't need a protocol:
typealias AnswerCallback<T> = (T) -> Void

How to use associated type protocol as argument's type to a function?

Here's the simplified code that I have:
class MyClass {
func returnSomething(argument: Protocol2) {}
}
protocol Protocol2: Protocol1 where E == Int {
}
protocol Protocol1 {
associatedtype E
func doSomething(_ value: E)
}
Compiler gives me the following error: Protocol 'Protocol2' can only be used as a generic constraint because it has Self or associated type requirements.
I understand that associated type E needs to be resolved before the protocol can be used as an argument in a function, but given that Protocol2 provides that information, why I still cannot compile that code?
Since I am learning about Swift generics , so I want to give it a go:
If you want to use Protocol2 as the type of argument in the function returnSomething
As suggested by #Nicolas Miari
class MyClass {
func returnSomething<T: Protocol2>(argument: T) {}
}
Now as the name suggests this function should return something
so
class MyClass {
func returnSomething<T: Protocol2>(argument: T) -> T {
return argument
}
}
Another issue that I see in the original problem is the use of where clause
I think you want to say the associated type of Protocol1 is Int
you should do it like this
protocol Protocol2: Protocol1 {
typealias E = Int
}
"How to use associated type protocol as argument's type to a function?"
I might not be following what you're asking here, but if a protocol of yours have an associated type, e.g.
protocol DummyNumericTypeWrapper {
associatedtype U: Numeric
static func magic(_ bar: U) -> U
}
You can access this type e.g. in a generic context where the generic placeholder has been constrained to your protocol. To follow up the dummy wrapper above with an entirely dummy example, e.g.:
extension StupidNumericTypeWrapper where U == Int {
static func magic(_ bar: Int) -> Int {
return bar + 42
}
}
func foo<T: StupidNumericTypeWrapper>(_ bar: T.U, _ _ : T.Type) -> T.U {
return T.magic(bar) * T.magic(bar)
}
struct DefaultMagic: StupidNumericTypeWrapper { }
let num = foo(1, DefaultMagic.self)
print(num) // 1849 (43*43)
Swift compiler converted my typealias to the where clause. (Typealias overriding associated type 'E' from protocol 'Protocol1' is better expressed as same-type constraint on the protocol)
Passing argument indeed works with aforementioned syntax, thank you all!
But how would you return an instance that conforms to Protocol2?
class ClassConformsToProto2: Protocol2 {
func doSomething(_ value: Int) {
}
}
class MyClass {
func returnSomething<T: Protocol2>() -> T {
return ClassConformsToProto2()
}
}
This code doesn't work.
Cannot convert return expression of type 'ClasConformsToProto2' to return type 'T'

How do I add different types conforming to a protocol with an associated type to a collection?

As an exercise in learning I'm rewriting my validation library in Swift.
I have a ValidationRule protocol that defines what individual rules should look like:
protocol ValidationRule {
typealias InputType
func validateInput(input: InputType) -> Bool
//...
}
The associated type InputType defines the type of input to be validated (e.g String). It can be explicit or generic.
Here are two rules:
struct ValidationRuleLength: ValidationRule {
typealias InputType = String
//...
}
struct ValidationRuleCondition<T>: ValidationRule {
typealias InputType = T
// ...
}
Elsewhere, I have a function that validates an input with a collection of ValidationRules:
static func validate<R: ValidationRule>(input i: R.InputType, rules rs: [R]) -> ValidationResult {
let errors = rs.filter { !$0.validateInput(i) }.map { $0.failureMessage }
return errors.isEmpty ? .Valid : .Invalid(errors)
}
I thought this was going to work but the compiler disagrees.
In the following example, even though the input is a String, rule1's InputType is a String, and rule2s InputType is a String...
func testThatItCanEvaluateMultipleRules() {
let rule1 = ValidationRuleCondition<String>(failureMessage: "message1") { $0.characters.count > 0 }
let rule2 = ValidationRuleLength(min: 1, failureMessage: "message2")
let invalid = Validator.validate(input: "", rules: [rule1, rule2])
XCTAssertEqual(invalid, .Invalid(["message1", "message2"]))
}
... I'm getting extremely helpful error message:
_ is not convertible to ValidationRuleLength
which is cryptic but suggests that the types should be exactly equal?
So my question is... how do I append different types that all conform to a protocol with an associated type into a collection?
Unsure how to achieve what I'm attempting, or if it's even possible?
EDIT
Here's it is without context:
protocol Foo {
typealias FooType
func doSomething(thing: FooType)
}
class Bar<T>: Foo {
typealias FooType = T
func doSomething(thing: T) {
print(thing)
}
}
class Baz: Foo {
typealias FooType = String
func doSomething(thing: String) {
print(thing)
}
}
func doSomethingWithFoos<F: Foo>(thing: [F]) {
print(thing)
}
let bar = Bar<String>()
let baz = Baz()
let foos: [Foo] = [bar, baz]
doSomethingWithFoos(foos)
Here we get:
Protocol Foo can only be used as a generic constraint because it has
Self or associated type requirements.
I understand that. What I need to say is something like:
doSomethingWithFoos<F: Foo where F.FooType == F.FooType>(thing: [F]) {
}
Protocols with type aliases cannot be used this way. Swift doesn't have a way to talk directly about meta-types like ValidationRule or Array. You can only deal with instantiations like ValidationRule where... or Array<String>. With typealiases, there's no way to get there directly. So we have to get there indirectly with type erasure.
Swift has several type-erasers. AnySequence, AnyGenerator, AnyForwardIndex, etc. These are generic versions of protocols. We can build our own AnyValidationRule:
struct AnyValidationRule<InputType>: ValidationRule {
private let validator: (InputType) -> Bool
init<Base: ValidationRule where Base.InputType == InputType>(_ base: Base) {
validator = base.validate
}
func validate(input: InputType) -> Bool { return validator(input) }
}
The deep magic here is validator. It's possible that there's some other way to do type erasure without a closure, but that's the best way I know. (I also hate the fact that Swift cannot handle validate being a closure property. In Swift, property getters aren't proper methods. So you need the extra indirection layer of validator.)
With that in place, you can make the kinds of arrays you wanted:
let len = ValidationRuleLength()
len.validate("stuff")
let cond = ValidationRuleCondition<String>()
cond.validate("otherstuff")
let rules = [AnyValidationRule(len), AnyValidationRule(cond)]
let passed = rules.reduce(true) { $0 && $1.validate("combined") }
Note that type erasure doesn't throw away type safety. It just "erases" a layer of implementation detail. AnyValidationRule<String> is still different from AnyValidationRule<Int>, so this will fail:
let len = ValidationRuleLength()
let condInt = ValidationRuleCondition<Int>()
let badRules = [AnyValidationRule(len), AnyValidationRule(condInt)]
// error: type of expression is ambiguous without more context

Pure Swift class conforming to protocol with static method - issue with upcasting

Given we have a Swift protocol with one static method:
protocol Creatable: class {
static func create() -> AnyObject
}
and a pure Swift class which conforms to the protocol:
class Foo : Creatable {
static func create() -> AnyObject {
return Foo() as AnyObject
}
}
Later on when one tries to make use of that protocol by operating on type Creatable e.g.:
var f : Creatable = Foo.self
f.create()
The compiler complains with the following:
error: type 'Foo.Type' does not conform to protocol 'Creatable'
The question is: is this a Swift limitation or I'm using the protocols and static/class method in the wrong way.
Objective-C equivalent would be something like:
Class someClass = [Foo class];
if ([someClass conformsToProtocol:#protocol(Creatable)]) {
[(Class <Foo>)someClass create];
}
A Creatable reference points to an instance of Foo, not to the Foo type itself.
To get the equivalent of the class-level protocol implementation, you need an instance of Creatable.Type:
let c: Creatable.Type = Foo.self
However, you’ll get an error when you then try to use it:
// error: accessing members of protocol type value 'Creatable.Type' is unimplemented
c.create()
All that said, is there a reason why you can’t just use functions to fulfill your requirement, instead of metatypes?
let f = Foo.create
// f is now a function, ()->AnyObject, that creates Foos
let someFoo = f()
Using .Type is the key:
var f : Creatable.Type = Foo.self
And this no longer gives the "unimplemented" error. See full code below:
protocol Creatable: class {
static func create() -> AnyObject
}
class Foo : Creatable {
static func create() -> AnyObject {
return Foo() as AnyObject
}
}
var f : Creatable.Type = Foo.self
f.create()

Resources