Is there a way to extend an Array when the element is a type of tuple ?
public extension Array where Element: (timeZoneName: String, formattedName: String){
}
This declaration returns 4 errors:
Statement cannot begin with a closure expression
Braced block statements is an unused closure
Expected '{' in extension
Expected identifier for type name
I can't tell if the errors shown are accurate.
Any ideas?
Swift 3 version of appzYourLife's answer:
extension Sequence where Iterator.Element == Tuple2 {
func foo() {}
}
It's now possible in Swift 3.1.
extension Array where Element == (String, String) {
...
}
Since (AFAIK) the Tuple type does not conform to a Protocol (and does not even have a name) it's very hard to do what you need.
This is the closest I could get (maybe others can provide more elegant solutions).
Typealias
First of all lets define a couple of typealiases
typealias Tuple2 = (Any, Any)
typealias Tuple3 = (Any, Any, Any)
Yes, some readers now understand where I am going and probably don't like it...
I don't like it neither
SequenceType
Now let's extend the protocol SequenceType adding the foo method when the Element of the sequence is Tuple2...
extension SequenceType where Generator.Element == Tuple2 {
func foo() {}
}
or Tuple3
extension SequenceType where Generator.Element == Tuple3 {
func foo() {}
}
Array
Next lets define and populate an array of Tuple2
let list: [Tuple2] = [(1,2)]
Now the extension is applied and we can write
list.foo()
Disclaimer :D
This does work only if the array is explicitly declared as [Tuple2] (or [Tuple3]).
Something like this does not work
let list = [(1,2)]
list.foo() // compile error
You can't add specific typing like extension Array where Element == Int because this would transform the generic Array into a non-generic version.
You will see an error something like same-type requirement makes generic parameter 'Element' non-generic
Edit
It does actually seem legit (at least in Swift 2.2) to do:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array where Element: tzTuple {
}
You will have to see if this works in runtime though.
I was checking this in a Playground and at present, Playgrounds are not yet fully functional with Swift 2.2-dev
I would suggest this instead:
typealias tzTuple = (timeZoneName: String, formattedName: String)
extension Array {
func formattedName(index: Int) -> String? {
if self[index] is tzTuple {
return (self[index] as! tzTuple).formattedName
}
return nil
}
}
will allow you to do
let foo = [(timezoneName: "PST", formattedName: "Pacific Standard Time"),(timezoneName: "AEST", formattedName: "Australian Eastern Time")]
print(foo.formattedName(0))
Related
I have faced an issue.
Consider I have one protocol and two classes implementing it:
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
then at some point I'm creating an array and passing it to function:
var array = [A]()
func update(object: Any){
}
update(object: array)
from that function update I would like to pass object to another function if it mets another function's conditions:
func process<T: Initiatable>(array: T){
/* ... */
}
So how can I check that object of type Any is an array of concrete class implementing Initiatable protocol? I would like to write something like
func update(object: Any){
if let array = object as Array<T: Initiatable>{
process(array: array)
}
}
But that's doesn't work. Code like:
func update(object: Any){
if let array = object as [Initiatable]{
process(array: array)
}
}
func process(array: [Initiatable]){ }
Compiles fine, but that's not what I want – process function should receive an array of concrete implementation of Initiatable so at some point it could use:
func process<T: Initiatable>(array: [T]){
/* other code */
T.init()
}
So is there any way to do this? Thank you very much in advance!
There's a few parts to this question:
Generating the array of types
Your array declaration is expecting an array of A objects rather than A types. To generate an array with A types, you could pass in a Postfix self expression: (link)
var array = [ A.self ]
This would define array as an array of A.Type, called the Metatype Type (same link).
You could also generate an empty array with this metatype type:
var array:[A.Type] = []
If you wanted an array with both A.self and B.self you could either specify it as [Any]...
var array:[Any] = [A.self,B.self]
...or, make use of the Initiatable protocol you created:
var array:[Initiatable.Type] = [A.self,B.self]
Downcasting an array to an array of types in your update method
You were having trouble downcasting an Any object to an array of types.
Here's my updated update method:
func update(object: Any){
if let array = object as? [Initiatable.Type] { //1
process(array: array)
}
}
You can now perform an optional downcast on the array that is being past into your update method. Downcast it to an array of metadata types of Initiatable: (This is the only line I modified from your method)
Receiving a type as a parameter in the process method
I'm assuming that you just want your process method to receive an array of types, and instantiate a variable based on one of those types. You didn't mention which element in the array, so I've just gone with the first.
func process(array: [Initiatable.Type]){ //1
if let firstType = array.first { //2
let firstObject = firstType.init() //3
}
}
The process method can receive an array of types that adopt the Initiatable protocol.
I'm using optional binding to get at the value of the first element in the array. This should be a type.
Instantiate an object based on the first element in the array.
If your array is nonempty, then you can solve this by grabbing the runtime type of one element from the array:
func update(object: Any){
if let array = object as? [Initiatable]{
process(array: array)
} else {
// Not initiatable
}
}
func process(array: [Initiatable]) { // no need for generics here
guard let first = array.first else {
// Unable to determine array type
// throw or return or whatever
}
// type(of: first) gives A or B
type(of: first).init()
}
If the array is empty, then I don’t know of a way to do this in Swift (as of Swift 3, anyway).
The static type of object is Any, so you need to use the runtime type of object in order to get anywhere.
However, Swift (and most languages with generics) only resolve type parameters like T based on the static types of the values you pass. Therefore your whole approach with process<T: Initiatable>(array: [T]) is a dead end: T can only take on a type the compiler already knew when you called process().
Swift does give you access to runtime types: type(of: object) will return (for example) Array<A>. So close! But:
AFAIK, there is currently no way to extract type parameters from a metatype, i.e. to get A from Array<A>, unless the compiler already knows the type parameter statically.
You can get the string "Array<A>" and extract the string A from it, but AFAIK there is no way to map strings back to types in Swift unless the type is an Objective-C class.
In short, I think you’ve just hit a limit of Swift’s metatype / reflection capabilities.
Unfortunately I can't think of a solution in a given constraints. It seems like you are trying to achieve compile-time polymorphism at runtime.
Using generics implies that compiler understands what class will be called. But using Any implies that at compile time it could be anything and it would be known only at runtime.
The closest solutions that I can propose are:
// Either use generic `update` function
func updateGeneric<T: Initiatable>(array: Array<T>){
process(array: array)
}
// or explicitly convert `Any` to the class,
// so compiler understands what implementation of `process` to call
func updateExplicit(object: Any) {
if let array = object as? [A] {
process(array: array)
}
if let array = object as? [B] {
process(array: array)
}
}
Is this help you,
protocol Initiatable{
init()
}
class A: Initiatable{
required init() {}
}
class B: Initiatable{
required init() {}
}
class Test<T : Initiatable>
{
func update(object: Any){
if let array = object as? [T]{
process(array: array)
}
}
func process(array: [T]){
}
}
I found a workaround to solve this problem:
func classTypeFrom(_ className: String) -> AnyClass!{
if let appName = Bundle.main.object(forInfoDictionaryKey: "CFBundleName") as? String {
let classStringName = "_TtC\(appName.characters.count)\(appName)\(className.characters.count)\(className)"
return NSClassFromString(classStringName)
}
return nil;
}
func update(object: Any){
if let array = object as? [Initiatable]{
let arrayTypeName = "\(type(of: ar))"
let objectTypeName = arrayTypeName.substringFrom(index: 6, length: arrayTypeName.characters.count - 7)
if let arrayType = classTypeFrom(objectTypeName) as? Initiatable.Type{
process(array: array, type: arrayType)
}
}
}
func process(array: [Initiatable], type: Initiatable.Type){
var ar = array
let newObj = type.init()
ar.append(newObj)
}
To gain a better understanding of Swift, I've extended SequenceType to add my own versions of map, reduce and forEach functions. However, the filter function is different in that it returns an array of Self.Generator.Element. Here's what I have so far:
extension SequenceType {
public func myFilter(#noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element] {
var array = [Self.Generator.Element]()
for element in self {
do {
if try includeElement (element) {
array.append(element)
}
}
}
return array
}
}
The statement var array = [Self.Generator.Element]() produces an "Invalid use of () to call a value of non-function type [Self.Generator.Element.Type]" error. My question is how do I create/add to/return an array of Self.Generator.Element?
Not sure why, but Xcode is fine with following syntax:
extension SequenceType {
public func myFilter(#noescape includeElement: (Self.Generator.Element) throws -> Bool) rethrows -> [Self.Generator.Element] {
var array : [Self.Generator.Element] = []
for element in self {
do {
if try includeElement (element) {
array.append(element)
}
}
}
return array
}
}
The only change is the array declaration (var array : [Self.Generator.Element] = []).
Swift's type inference is getting confused about what you mean. This could mean two things:
var array = [Self.Generator.Element]()
You might mean (and do mean) that you want to call the constructor for the type Array<Self.Generator.Element>.
But you might mean (and the compiler thinks you mean) that you want to create an array of Self.Generator.Element.Type and then you want to call it as a function.
I don't remember this being a compiler confusion in the past, and it may be a regression. That said, it is ambiguous (though only one really makes sense in the end, and I would think you'd need to have said Generator.Element.self there to refer to the type, so maybe it's a real compiler bug). You can remove the ambiguity by using the preferred way to initialize arrays, by using their type:
var array: [Self.Generator.Element] = []
While find(["a", "b"], "c") works with no problems, I get an error when trying to find the index of a structure inside an array of structures:
struct Score
{
//...
}
var scores: [Score] = //...
var score: Score = //...
find(self.scores, score) // Error: Cannot invoke 'find' with an argument list of type '([Score], Score)'
I though it could be a problem with structures that can't be compared to each other by default. But changing Scores definition to class
gives me the same error.
EDIT: as of Swift 2.0, there is now a built-in version of find that takes a closure so you don’t have to write your own – but also, find has been renamed indexOf, and is now a protocol extension of CollectionType, so you call it like a method:
// if you make `Score` conform to `Equatable:
if let idx = self.scores.indexOf(score) {
}
// or if you don't make it Equatable, you can just use a closure:
// (see original answer below for why you might prefer to do this)
if let idx = scores.indexOf({$0.scoreval == 3}) {
}
Original pre-2.0 answer below
While the answers suggesting making your class Equatable may work nicely, I'd recommend a bit of caution before choosing to do this. The reason being that, as the docs state, equatability implies substitutability, and your == operator must be reflexive, symmetric and transitive. If you don't adhere to this, you might get some very weird behavior when using algorithms like equals, sort etc. Be especially cautious if implementing Equatable on non-final classes. If you're sure you can meet requirements, go for it, and find will work.
If not, an alternative you could consider is writing a function that ought to be in the standard library but isn't, which is a find that takes a closure:
func find<C: CollectionType>(source: C, match: C.Generator.Element -> Bool) -> C.Index {
for idx in indices(source) {
if match(source[idx]) { return idx }
}
return nil
}
Once you have this, you can supply any matching criteria you prefer. For example, if your objects are classes you could use reference equality:
let idx = find(scores) { $0 === $1 }
The interface for the function find is/was:
func find<C : CollectionType where C.Generator.Element : Equatable>(domain: C,
value: C.Generator.Element) -> C.Index?
This says that the CollectionType of C must have elements that are Equatable and, furthermore, that the value must also be Equatable.
[Note Swift 3.0: As of Swift 3.0, you'll need to use the index function which comes in two variations. In the first, you'll supply your own predicate:
func index(where: (Self.Generator.Element) -> Bool) -> Self.Index?
In the second, your elements need to be equatable:
// Where Generator.Element : Equatable
func index(of: Self.Generator.Element) -> Self.Index?
If you decide to go the equatable route, then the following applies.
Note End]
Your Score struct is not Equatable and hence the error. You'll need to figure out what it means for scores to be equal to one another. Maybe it is some numerical 'score'; maybe it is a 'score' and a 'user id'. It depends on your Score abstraction. Once you know, you implement == using:
func == (lhs:Score, rhs:Score) -> Bool {
return // condition for what it means to be equal
}
Note: if you use class and thus scores have 'identity' then you can implement this as:
func == (lhs:Score, rhs:Score) -> Bool { return lhs === rhs }
Your example with strings works because String is Equatable. If you look in the Swift library code you'll see:
extension String : Equatable {}
func ==(lhs: String, rhs: String) -> Bool
As the others have said, the objects you search for must conform to the Equatable protocol.
So you need to add an extension to your Score struct that tells the compiler that it conforms to that protocol:
extension Score: Equatable {}
And then you need to implement the == function for that class:
public func ==(lhs: Score, rhs: Score) -> Bool
{
return lhs.whatever == rhs.whatever //replace with code for your struct.
}
In Swift I declared a function that differs from Array.count only in that if array == nil the function returns 0. This is related to my UITableViewDataSource, but that's not important here. The problem is, if I declare the function as:
class func countOfItemsInArray(array: [AnyObject]?) -> Int
and then try to pass it an array of structs, it declares that the structs in the array do not conform to AnyObject. I understand why that is (I think), but is there a way to make this work with classes and structs, or should I just give in to copy and paste?
Generics are probably better suited to this problem than relying on covariance of [AnyObject]. A version of countElements that worked on an optional array and returned 0 in case of nil could go like this:
func countElements<T>(array: [T]?) -> Int {
return array?.count ?? 0
}
When you call countElements with any kind of array, the placeholder T is replaced with the type of the element contained in the array.
Note, this version overloads the existing countElements with a version that takes an optional. If you call it with a non-optional or any other kind of collection, the Swift version would be called, if you pass in an optional array, this one will be called. It’s debatable whether this is a good practice (I think it’s fine) or a bad one (some may disapprove :).
A version that works on any collection type would be:
func countElements<C: CollectionType>(col: C?) -> C.Index.Distance {
return col.map { countElements($0) } ?? 0
}
If you use Any instead of AnyObject you can pass any type, so also structs:
class func countOfItemsInArray(array: [Any]?) -> Int
This is kind of weird.
I used this function:
func countOfItemsInArray(array: [Any]?) -> Int {
return array != nil ? array!.count : 0
}
Declared two of your Assignment structs and put them in an array:
let structOne = Assignment(name: "1", dueDate: NSDate(), subject: "1")
let structTwo = Assignment(name: "2", dueDate: NSDate(), subject: "2")
let myArray: [Assignment] = [structOne, structTwo]
But here's the interesting part.
When calling println(countOfItemsInArray(myArray)) it gives the error:
<stdin>:27:33: error: 'Assignment' is not identical to 'Any'
println(countOfItemsInArray(myArray))
^
<stdin>:17:26: note: in initialization of parameter 'array'
func countOfItemsInArray(array: [Any]?) -> Int {
^
So I tested if myArray is of type [Any]:
println(myArray is [Any])
to which swift says:
<stdin>:25:17: error: 'Any' is not a subtype of 'Assignment'
println(myArray is [Any])
^
But when I change the type annotation of myArray to [Any] it works:
let myArray: [Any] = [structOne, structTwo]
And when simply handing the literal to the function it works, too:
countOfItemsInArray([structOne, structTwo])
The whole code example can be seen here.
Whenever I create an array of Protocol Types, how would I obtain the index of an object in that array? I've tried the below:
protocol aProtocol {
func doSomething()
}
class aClass: aProtocol, Equatable {
var aProperty = "test"
func doSomething() {
}
}
func == (lhs: aClass, rhs: aClass) -> Bool {
return lhs.aProperty == rhs.aProperty
}
var testArray = aProtocol[]()
let testObject = aClass()
testArray += testObject
find(testArray, testObject)
In this case I get a "Cannot convert the expression's type '$T4?' to type 'aClass'" error.
Looking at find()'s method signature we find that the element should implement Equatable (which is why I overload the == operator above):
func find<C : Collection where C.GeneratorType.Element : Equatable>(domain: C, value: C.GeneratorType.Element) -> C.IndexType?
Does anyone have any insight as to what I'm doing wrong or if this scenario is even possible? Thanks.
If find() expects its first argument to have elements that are Equatable and its second argument to be Equatable too, then invoking find() on an array of non-Equatable aProtocol elements won't compile.
To fix, change to
protocol aProtocol : Equatable { ... }
and then implement the Equatable protocol for your aClass definition.
[I'm not in front of a Mac at the moment, so I can't verify this suggestion.]
You overload the == function, but you don't mark aClass as conforming to the Equatable protocol.
https://developer.apple.com/library/prerelease/ios/documentation/General/Reference/SwiftStandardLibraryReference/Equatable.html