Can't Create Default Closure Parameter in Array Extension Method in Swift

Can't create default closure parameter in Array extension method in Swift

The elements in an array don't have to be Comparable, which means you can't write that function for all possible Arrays.

Unfortunately Swift doesn't allow you to extend just a subset of Arrays. In other for your function to work, the compiler needs extra type information to infer the type C.

Others have struggled with this same issue:

How can we create a generic Array Extension that sums Number types in Swift?

Instead of using an extension, you should be able to use minimum as a global function.

Why doesn't Array.sorted() have default closure?

In let list2 = sorted(list) the function

func sorted<C : SequenceType where C.Generator.Element : Comparable>(source: C) -> [C.Generator.Element]

is called. This function is only defined for sequences for which the
element type is Comparable, i.e. can be compared with <.

On the other hand, it is not possible to define an array extension
method sorted() which applies only to arrays with comparable elements,
compare

  • Array extension to remove object by value: "You cannot write a method on a generic type that is more restrictive on the template."
  • Is it possible to make an Array extension in Swift that is restricted to one class?

Apple's Swift compiler complains of partial application when creating extension method on Array

To use an operator as a function, surround it in parens:

var min = array.comparest({ $0 }, (<))!

For example:

[1,2,3].reduce(0,(+)) // -> 6

Method signature to take the same array in array extension

Just simply pass parameter of type Array

extension Array {
func doSomethingWithAnotherArray(arr: Array) {
... // do something
}
}

[Int].doSomethingWithAnotherArray(arr: [Int]) // works
[Int].doSomethingWithAnotherArray(arr: [String]) // doesn't work

Is it possible to make an Array extension in Swift that is restricted to one class?

As of Swift 2, this can now be achieved with protocol extensions,
which provide method and property implementations to conforming types
(optionally restricted by additional constraints).

A simple example: Define a method for all types conforming
to SequenceType (such as Array) where the sequence element is a String:

extension SequenceType where Generator.Element == String {
func joined() -> String {
return "".join(self)
}
}

let a = ["foo", "bar"].joined()
print(a) // foobar

The extension method cannot be defined for struct Array directly, but only for all types
conforming to some protocol (with optional constraints). So one
has to find a protocol to which Array conforms and which provides all the necessary methods. In the above example, that is SequenceType.

Another example (a variation of How do I insert an element at the correct position into a sorted array in Swift?):

extension CollectionType where Generator.Element : Comparable, Index : RandomAccessIndexType {
typealias T = Generator.Element
func insertionIndexOf(elem: T) -> Index {
var lo = self.startIndex
var hi = self.endIndex
while lo != hi {
// mid = lo + (hi - 1 - lo)/2
let mid = lo.advancedBy(lo.distanceTo(hi.predecessor())/2)
if self[mid] < elem {
lo = mid + 1
} else if elem < self[mid] {
hi = mid
} else {
return mid // found at position `mid`
}
}
return lo // not found, would be inserted at position `lo`
}
}

let ar = [1, 3, 5, 7]
let pos = ar.insertionIndexOf(6)
print(pos) // 3

Here the method is defined as an extension to CollectionType because
subscript access to the elements is needed, and the elements are
required to be Comparable.

Using a property as a default parameter value for a method in the same class

I don't think you're doing anything wrong.

The language specification only says that a default parameter should come before non-default parameters (p169), and that the default value is defined by an expression (p637).

It does not say what that expression is allowed to reference. It seems like it is not allowed to reference the instance on which you are calling the method, i.e., self, which seems like it would be necessary to reference self.niceAnimal.

As a workaround, you could define the default parameter as an optional with a default value of nil, and then set the actual value with an "if let" that references the member variable in the default case, like so:

 class animal {
var niceAnimal: Bool
var numberOfLegs: Int

init(numberOfLegs: Int, animalIsNice: Bool) {
self.numberOfLegs = numberOfLegs
self.niceAnimal = animalIsNice
}

func description(numberOfLegs: Int, animalIsNice: Bool? = nil) {
if let animalIsNice = animalIsNice ?? self.niceAnimal {
// print
}
}
}

Subclass Type as closure parameter

Ok, so I've been working on this for a day and I have found a way to do it using the method in this answer for the didChange and initObserver functions and taking inspiration from this way of saving data in extensions.

First off, all the functions that need to use the type of the subclass are moved to a protocol.

protocol FirebaseObjectType {}

extension FirebaseObjectType where Self: FirebaseObject {
private func initObserver(at ref: DatabaseReference) {
//...
}

mutating func didChange(completion: @escaping (((Self) -> Void))) {
if observer == nil {
// init Firebase observer here so there will be no Firebase
// observer running when you don't check for changes of the
// object, and so the Firebase call uses the type of whatever
// FirebaseObject this function is called on eg:
// RecipeItem.didChange returns RecipeItem
// and NOT:
// RecipeItem.didChange returns FirebaseObject
initObserver(at: ref)
}
if closureWrapper == nil {
// init closureWrapper here instead of in init() so it uses
// the class this function is called on instead of FirebaseObject
closureWrapper = ClosureWrapper<Self>()
}

// Save closure for future updates to object
closures.append(completion)

// Activate closure with current object
completion(self)
}
}

To save the closures I now use a wrapper class so I can do type checking on that. In FirebaseObject:

class ClosureWrapper<T> {
var array: [((T) -> Void)]

init() {
array = []
}
}

fileprivate var closureWrapper: AnyObject?

Now I can get the closures with the right type in FirebaseObjectType protocol:

private var closures: [((Self) -> Void)] {
get {
let closureWrapper = self.closureWrapper as? ClosureWrapper<Self>

return closureWrapper?.array ?? []
}
set {
if let closureWrapper = closureWrapper as? ClosureWrapper<Self> {
closureWrapper.array = newValue
}
}
}

I can now use didChange on a FirebaseObject subclass without checking its type every time.

var recipe = RecipeItem(data)

recipe.didChange { newRecipe in
// Do something with newRecipe
}

Swift Generics Type Inference Extensions

When you define max (and min) like this:

func max<T, U : Comparable>(f: T -> U ) -> U?

You're actually saying that the closure f might take a different type than the elements in the Array. Since Array is already a generic struct Array<T>, you can re-use the element type T that it's already defined. From the Swift language guide:

When you extend a generic type, you do not provide a type parameter
list as part of the extension’s definition. Instead, the type
parameter list from the original type definition is available within
the body of the extension, and the original type parameter names are
used to refer to the type parameters from the original definition.

So, since the element type T is already available to you, simply take the T out of the type parameter list, like so:

func max<U : Comparable>(f: T -> U ) -> U?

That guarantees that the closure f will take the same type T that the elements in the Array have. Then the compiler can correctly infer which types you are using in your test cases and fixes the errors you're seeing.



Related Topics



Leave a reply



Submit