What type should I use in swift 5 if I want a function to accept either a [Double] or an ArraySlice<Double>?
In other words, is there some type I can use which would allow me to combine both of the following declarations into one, so that the function can be called with one or the other?
func appendToX(_ data: ArraySlice<Double>)
func appendToX(_ data: [Double])
As #MartinR said in the comment, they both conform to RandomAccessCollection protocol, but you need to use generics in your function due to the fact that RandomAccessCollection has Self or associated type requirements:
func appendToX<T: RandomAccessCollection>(_ data: T) where T.Element == Double, T.Index == Int {
}
Swift 5.6
Thanks to several recent improvements to Swift's generics UI, you can replace your original two function declarations with the following:
func appendToX(_ data: any RandomAccessCollection<Double>)
Related
I am curious if there is any way in Swift to declare a multi-dimensional array that has a specific type(for this example we'll say Float) but isn't confined to a fixed number of dimensions(E.g., let arr: Array<Array<Float>>)?
I'd like to create a function that accepts a multi-dimensional array and currently the only way I'm aware of is:
func testFunction(arr: [Any]) {}
This is problematic though as it doesn't specify the actual scalar type of the array. As the type Any implies, this function can accept anything that is stuck inside of an Array. Any ideas?
Not with just an Array. Each array needs to know what type it contains. To contain an arbitrary number of dimensions, each array would need to be able to hold either a Float or an Array.
You could possible do something custom with an enum that has two cases.
enum MultidimensionalArray<Element> {
case array([MultidimensionalArray<Element>])
case values([Element])
}
but you'll probably have add a bunch more methods to it based on how you want to use ie.
How about generics
func testFunction<T>(arr: [T]) { }
T is a specific type and can be anything even
let foo = [[[[Float]]]]()
testFunction(arr: foo)
I found a way that makes it pretty simple to do what I'm after with some generic constraints and extensions.
protocol TypedNDArrayProtocol {}
// Make the type you are trying enforce conform to our protocol
extension Float: TypedNDArrayProtocol {}
// Now make Array conform to the same protocol while constraining
// it to only elements that *also* conform to this protocol
extension Array: TypedNDArrayProtocol where Element: TypedNDArrayProtocol {}
func testFunction(arr: Array<TypedNDArrayProtocol>) {}
This creates a sort of recursive conformance where the Array is allowed to accept either another Array, or a Float. If it is an Array, that Array can again only accept another Array or a Float and so on. This way you can use an arbitrary number of dimensions, but it must eventually terminate with a specific type; in this case a Float.
// This will work
testFunction(arr: [1.0, 2.0])
// This will also work
testFunction(arr: [[[3.0], [4.0]], [[5.0], [6.0]]])
// This will NOT work
testFunction(arr: [[[3], [4]], [[5], [6]]])
// Neither will this
testFunction(arr: [[["3"], ["4"]], [["5"], ["6"]]])
I am attempting to extend Array<MutatingCollection> so I can mirror the contents of an Array of Arrays, but the compiler says I can't call reverse() on the elements in the array, despite reverse() being defined in MutatingCollection protocol.
I want to do something like this:
var table = [[0,1,2],
[3,4,5],
[6,7,8]]
table.mirror()
//table now [[2,1,0],
// [5,4,3],
// [8,7,6]]
Here is my (not working) code:
extension Array where Element == MutableCollection {
mutating func mirror() {
for index in self.indices {
self[index].reverse()
}
}
}
I have tried it as self.map {array in array.reverse()} as well (which I think does the same thing, but I don't fully grok map()) Both ways result in the same error message:
Member 'reverse' cannot be used on value of type 'MutableCollection'
Edit: I can call the same code directly and it works as I intended.
Playgrounds Screenshot
Perhaps I'm using extension improperly, or Swift Playgrounds is blocking my access somehow.
First of all, the extension should be declared like this:
extension Array where Element : MutableCollection {
You want to check that Element adheres to the protocol MutableCollection, not that it is a MutableCollection
However, then I'm not able to call the reverse method on the subscript for some reason. The best I've been able to do is this:
extension Array where Element : MutableCollection {
mutating func mirror() {
for index in self.indices {
self[index] = self[index].reversed() as! Element
}
}
}
Which works as you need it to work although the forced cast is very ugly and I dislike doing it. I suppose I should test the cast to be certain but I can't see any case where calling reversed() would result in a collection that couldn't be cast back to Element.
Edit:
I figured out the issue. The reverse() method is only valid on MutableCollection when it is also a BidirectionalCollection. This code now works correctly:
extension MutableCollection where
Iterator.Element : MutableCollection &
BidirectionalCollection,
Indices.Iterator.Element == Index {
mutating func mirror() {
for index in self.indices {
self[index].reverse()
}
}
}
Now the code should work for all MutableCollection whose elements are both a MutableCollection and BidirectionalCollection - such as [Array<Int>] or even [ArraySlice<Int>]
You can see the full code for reverse() in Swift 3.1 here:
Reverse.swift
extension MutableCollection where Self : BidirectionalCollection
I'm playing around with Swift trying to make it look more "dynamically typed" – just for fun, no production value expected.
Now I'm stuck with overwriting behavior of converting builtin types to String.
For example, I'd like to see this output for Array:
let nums = [1, 2, 3]
print(nums) // "I'm an array"
So far I tried to
make an extension to NSArray (not compiles)
implement CustomStringConvertible (not compiles)
make an extension to Array (compiles, changes nothing)
Seems like I'm on the wrong path:
extension Array: CustomStringConvertible {
public var description: String { return "An array" }
}
gives the warning:
Conformance of 'Array' to protocol 'CustomStringConvertible' was already stated in the type's module 'Swift'
Is this doable in Swift?
This does not work because Array overrides description. If array did not override it then it would print "An array". The class method 'wins' over the extension.
extension Array {
public var description: String { return "An array" }
}
You could create a Wrapper class for your array. It's a workaround but doesn't override array's description itself.
class ArrayWrapper<T> : CustomStringConvertible{
var array : Array<T> = Array<T>()
var description: String { return "An array" }
}
You could then use it like this.
var array = ArrayWrapper<Int>()
array.array = [1,2,3]
print(array) //prints "An Array"
print(array.array) //still prints "[1, 2, 3]"
Seems like it is not possible to neither subclass nor overwrite a builtin Array type.
We could use a wrapper though (credits to #Yannick) and implement a ArrayLiteralConvertible protocol, so we could use square brackets for initialization.
struct Array<T> {
let array: [T]
}
extension Array: ArrayLiteralConvertible {
init(arrayLiteral elements: T...) {
self.array = elements
}
}
extension Array: CustomStringConvertible {
var description: String { return "An array" }
}
let array = [1,2,3]
print(array) // "An array\n"
Swift does not allow you to provide a protocol override for a type that already declares that protocol:
Imagine that you have a set of integers and you want to override the default implementation of CustomStringConvertible for a Set
The simplest answer to all of this is: you can't, and that's a feature not a bug. Set isn't your type and you don't get to change its already-defined behavior like this.
The capability to intercept and alter the behavior of a type to do something different to what that type is originally designed to do is powerful, but fraught with downsides. In this case, you've chosen a fairly benign thing to alter, but in other cases doing this could do all sorts of damage to assumed invariants of a type by changing its program-wide behavior.
The best way to alter a type to do something different like this is to wrap it in your own low-cost struct. Unfortunately this does mean writing a fair amount of boilerplate for forwarding – though less and less these days as we gain features like synthesized conformances and dynamic member lookup. Hopefully someday we'll get features that do make easier to create a newtype with customized behavior.
https://forums.swift.org/t/whats-the-best-way-to-override-customstringconvertible-for-a-collection/24844
How would a simple function like index(of:) be implemented in a swift 3 extension. Please provide an example with the practices required to implement the example are.
If you are looking for the syntax of an extension of Array in Swift 3 here it is
extension Array {
func foo() { }
}
You can also restrict the extension to be available only where the Element of the array does conform to some protocol
extension Array where Element : Equatable {
func foo() { }
}
I have an array of type [String]
let names = ["Joffrey", "Cersei", "Mountain", "Hound"]
I have a function which takes an array of [Any] type.
func printItems(items: [Any]){
for item in items {
print(item)
}
}
Now when I call the function with names as parameters,
printItems(names)
I get an error Cannot invoke 'printItems' with an argument list of type '([String])'.
Any is just a typealias for a protocol which all types implicitly conform to.
Thoughts?
This is a surprising limitation of Swift. You can't cast an array to type [Any] so you can't pass it to a function taking type [Any]. You can use map to cast each item of the array:
printItems(names.map {$0 as Any})
But, the right way to do this in Swift is to use Generics:
func printItems<T>(items: [T]) {
for item in items {
print(item)
}
}
let names = ["Joffrey", "Cersei", "Mountain", "Hound"]
let numbers = [3.1416, 2.71818, 1.4142, 1.618034]
printItems(names) // This now works
printItems(numbers) // This works too
While every type conforms Any, this is not the same as it being a universal implicit superclass that all types inherit from.
When you cast a type to a protocol, you create a new value with a different structure. So for a string to be of type Any, it needs to be physically transformed from the String representation:
sizeof(String) // 24 bytes (on 64-bit, anyway)
to the Any representation:
sizeof(Any) // 32 bytes, includes some meta data
// about what the type really is
Since value types are held directly in the array, the array would be a very different shape so under the hood the compiler would have to do the equivalent of this:
names.map { $0 as Any } // create a new array, with the Any versions
Swift could perhaps automate this process for you (it does if you pass a single variable into a function that takes Any). But personally I’m glad it doesn’t, I’d rather this be more explicit – suppose your array was huge, this would be a lot of processing happening implicitly under the hood.
This is different from when you have an array of reference types, all of which are pointers to the actual data and so all the same size, and which need no transformation when upcasting:
class C { }
class D: C { }
let d = D()
let c: C = d
unsafeBitCast(d, UnsafePointer<Void>.self) // these value will
unsafeBitCast(c, UnsafePointer<Void>.self) // be the same
So saying “this array of [D] is really an array of [C]” is just a matter of the compiler agreeing the types can be substituted, no data transformation needs to take place:
// so this works fine,
// no runtime transformation needed:
func f(cs: [C]) { }
let ds = [D(),D()]
f(ds)
But protocols still are different from superclass references when used with classes:
protocol P { }
extension C: P { }
sizeofValue(C()) // 8 bytes (just a pointer)
sizeofValue(C() as P) // 40 bytes
func g(ps: [P]) { }
g(ds) // won’t compile, needs transformation
You need to explicitly put in your let declaration that you
are declaring an Any array and not a specific type array.
With your current declaration, Swift will evaluate it to [String] array.
and not as [Any] array.
To fix your problem just do the following:
let names : [Any] = ["Joffrey", "Cersei", "Mountain", "Hound"]
If you want to retain your let declaration, use AnyObject
in your printItems function and it will be accepted by Swift.
func printItems(items: [AnyObject]){
for item in items {
print(item)
}
}