Overloading function in extension produces errors - swift

I tried to add append overload for Swift Array
import Foundation
extension Array {
func append<From>(from: [From], transformer: (From) -> [Element]) {
from.forEach {
self.append(contentsOf: transformer($0))
}
}
}
And it shows me compilation error: Error:(47, 24) extraneous argument label 'contentsOf:' in call
it looks like I can't use other overloads of append in my own overload. It's really strange. Can you help?

That error is misleading. Your append function needs to be marked as mutating:
extension Array {
mutating func append<From>(from: [From], transformer: (From) -> [Element]) {
from.forEach {
self.append(contentsOf: transformer($0))
}
}
}
Alternate Implementation
(This might not be what you need, but it could help another user.)
If you make your transformer to be (From) -> Element, you can simplify this to:
extension Array {
mutating func append<From>(from: [From], transformer: (From) -> Element) {
self.append(contentsOf: from.map(transformer))
}
}
Example call:
var strings = ["a", "b", "c"]
strings.append(from: [1, 2, 3], transformer: String.init)
print(strings) // prints ["a", "b", "c", "1", "2", "3"]

Related

How can I define `unlines` function generically?

I would like to define a unlines function which works which any Sequence whose elements conform to the StringProtocol; this is my attempt:
func unlines<S: StringProtocol>(_ xs: Sequence<S>) -> S {
return xs.joined(separator: "\n")
}
Error: Use of protocol 'Sequence' as a type must be written 'any Sequence'
A version defined for a concrete type does work:
func unlines(_ xs: [String]) -> String {
return xs.joined(separator: "\n")
}
but can only be applied with list of String.
How can I develop a general definition ?
EDIT:
For example, I would like to apply it to the following value:
["Hello", "World"].lazy.map { $0.lowercased() }
joined returns a String so you need to change the return type and use any Sequence
func unlines<S: StringProtocol>(_ xs: any Sequence<S>) -> String {
return xs.joined(separator: "\n")
}
This then works both with String and Substring
String example:
let result = unlines(["A", "B", "C"])
Substring example:
let result = unlines("A-B-C".split(separator: "-"))
both returns
A
B
C
In Swift, you'd typically use a protocol extension to define instance functions that should operate on an instance, rather than using free functions that take a first argument.
Here's how that might work:
extension Sequence where Element: StringProtocol {
// FIXME: This is a pretty Haskelly, non-Swifty name
func unlines() -> String {
joined(separator: "\n")
}
}
let input = ["Hello", "World"]
.lazy
.map { $0.lowercased() }
.unlines()
print(input)
// Prints:
// hello
// world

swift: AnyCollection with different generic types

AnyCollection([1, 2, 3]) // AnyCollection<Int>
AnyCollection(["a", "b", "c"]) // AnyCollection<String>
These two AnyCollection is great, but they have different Generic types, namely Int and String.
But I can still add these two AnyCollection to a single array, like this
// [AnyCollection<Any>]
let array = [AnyCollection([1, 2, 3]), AnyCollection(["a", "b", "c"])]
I can't understand what happened to these two AnyCollection.
Why can it convert from String or Int to Any ??
I wrote some code to create one myself.
// ❌ Heterogeneous collection literal could only be inferred to '[Any]'; add explicit type annotation if this is intentional
// Contains AnyMyCollection<String> && AnyMyCollection<Int> at a single collection.
var collections = [AnyMyCollection(Sports()), AnyMyCollection(Animals()), AnyMyCollection(Digits())]
protocol MyCollection<Element> {
associatedtype Element
func allValues() -> [Element]
}
// MARK: - AnyMyCollection
struct AnyMyCollection<Element> {
internal var _box: _AnyMyCollectionBase<Element>
init<C: MyCollection>(_ base: C) where C.Element == Element {
self._box = _MyCollectionBox(base)
}
}
extension AnyMyCollection: MyCollection {
func allValues() -> [Element] {
_box.allValues()
}
}
final class _MyCollectionBox<Base: MyCollection>: _AnyMyCollectionBase<Base.Element> {
init(_ base: Base) {
_base = base
}
private var _base: Base
override func allValues() -> [Base.Element] {
_base.allValues()
}
}
class _AnyMyCollectionBase<Element>: MyCollection {
func allValues() -> [Element] {
return []
}
}
// MARK: - Diffrent Types of My Collection
struct Animals: MyCollection {
typealias Element = String
func allValues() -> [Element] {
["Monkey", "Tiger", "Lion"]
}
}
struct Sports: MyCollection {
typealias Element = String
func allValues() -> [Element] {
["Basketball", "Football", "Baseball"]
}
}
struct Digits: MyCollection {
typealias Element = Int
func allValues() -> [Element] {
[1, 2, 3, 4, 5]
}
}
I tried to follow the same technique but failed because the type of the element in AnyMyCollection is not the same.
The simple explanation is that you are not Apple.
Generics in general are not covariant over the parameterized type, and you have no way to create to generic that is covariant over the paramterized type. But Apple does.
To see this more simply, consider Array. It is a generic. If you have two classes, a class and its subclass, you can combine arrays of each of them:
class MyClass {}
class MySubclass: MyClass {}
let arr1 = [MyClass()]
let arr2 = [MySubclass()]
let arr3 = [arr1, arr2] // [[MyClass]]
The compiler accepts this; it treats the resulting combination as an array of arrays of the superclass, Array<Array<MyClass>>. So for Apple, an Array<MySubclass> is treated as a sort of subclass of Array<MyClass>.
But now try to do that with your own generic. You can't do it:
class MyGeneric<T> {}
let g1 = MyGeneric<MyClass>()
let g2 = MyGeneric<MySubclass>()
let arr4 = [g1, g2] // error
The compiler does not magically see this as an Array<MyGeneric<MyClass>>. That's because your generic is not covariant.
There are always language proposals sitting around, hoping to allow us ordinary human beings to make covariant generics. But so far, none of them has arrived into the language.

Difference between flatMap and compactMap in Swift

It seems like in Swift 4.1 flatMap is deprecated. However there is a new method in Swift 4.1 compactMap which is doing the same thing?
With flatMap you can transform each object in a collection, then remove any items that were nil.
Like flatMap
let array = ["1", "2", nil]
array.flatMap { $0 } // will return "1", "2"
Like compactMap
let array = ["1", "2", nil]
array.compactMap { $0 } // will return "1", "2"
compactMap is doing the same thing.
What are the differences between these 2 methods? Why did Apple decide to rename the method?
The Swift standard library defines 3 overloads for flatMap function:
Sequence.flatMap<S>(_: (Element) -> S) -> [S.Element]
Optional.flatMap<U>(_: (Wrapped) -> U?) -> U?
Sequence.flatMap<U>(_: (Element) -> U?) -> [U]
The last overload function can be misused in two ways:
Consider the following struct and array:
struct Person {
var age: Int
var name: String
}
let people = [
Person(age: 21, name: "Osame"),
Person(age: 17, name: "Masoud"),
Person(age: 20, name: "Mehdi")
]
First Way: Additional Wrapping and Unwrapping:
If you needed to get an array of ages of persons included in people array you could use two functions :
let flatMappedAges = people.flatMap({$0.age}) // prints: [21, 17, 20]
let mappedAges = people.map({$0.age}) // prints: [21, 17, 20]
In this case the map function will do the job and there is no need to use flatMap, because both produce the same result. Besides, there is a useless wrapping and unwrapping process inside this use case of flatMap.(The closure parameter wraps its returned value with an Optional and the implementation of flatMap unwraps the Optional value before returning it)
Second Way - String conformance to Collection Protocol:
Think you need to get a list of persons' name from people array. You could use the following line :
let names = people.flatMap({$0.name})
If you were using a swift version prior to 4.0 you would get a transformed list of
["Osame", "Masoud", "Mehdi"]
but in newer versions String conforms to Collection protocol, So, your usage of flatMap() would match the first overload function instead of the third one and would give you a flattened result of your transformed values:
["O", "s", "a", "m", "e", "M", "a", "s", "o", "u", "d", "M", "e", "h", "d", "i"]
So, How did they solve it? They deprecated third overload of flatMap()
Because of these misuses, swift team has decided to deprecate the third overload to flatMap function. And their solution to the case where you need to to deal with Optionals so far was to introduce a new function called compactMap() which will give you the expected result.
There are three different variants of flatMap. The variant of Sequence.flatMap(_:) that accepts a closure returning an Optional value has been deprecated. Other variants of flatMap(_:) on both Sequence and Optional remain as is. The reason as explained in proposal document is because of the misuse.
Deprecated flatMap variant functionality is exactly the same under a new method compactMap.
See details here.
High-order function - is a function which operates by another function in arguments or/and returned. For example - sort, map, filter, reduce...
map vs compactMap vs flatMap
[RxJava Map vs FlatMap]
map - transform(Optional, Sequence, String)
flatMap - flat difficult structure into a single one(Optional, Collection)
compactMap - next step of flatMap. removes nil
flatMap vs compactMap
Before Swift v4.1 three realisations of flatMap had a place to be(without compactMap). That realisation were responsible for removing nil from a sequence. And it were more about map than flatMap
Experiments
//---------------------------------------
//map - for Optional, Sequence, String, Combine
//transform
//Optional
let mapOptional1: Int? = Optional(1).map { $0 } //Optional(1)
let mapOptional2: Int? = Optional(nil).map { $0 } //nil
let mapOptional3: Int?? = Optional(1).map { _ in nil } //Optional(nil)
let mapOptional4: Int?? = Optional(1).map { _ in Optional(nil) } //Optional(nil)
//collection
let mapCollection1: [Int] = [1, 2].map { $0 } //[1, 2]
let mapCollection2: [Int?] = [1, 2, nil, 4].map { $0 } //Optional(1), Optional(2), nil, Optional(4),
let mapCollection3: [Int?] = ["Hello", "1"].map { Int($0) } //[nil, Optional(1)]
//String
let mapString1: [Character] = "Alex".map { $0 } //["A", "l", "e", "x"]
//---------------------------------------
//flatMap - Optional, Collection, Combime
//Optional
let flatMapOptional1: Int? = Optional(1).flatMap { $0 } //Optional(1)
let flatMapOptional2: Int? = Optional(nil).flatMap { $0 } //nil
let flatMapOptional3: Int? = Optional(1).flatMap { _ in nil }
let flatMapOptional4: Int? = Optional(1).flatMap { _ in Optional(nil) }
//Collection
let flatMapCollection1: [Int] = [[1, 2], [3, 4]].flatMap { $0 } //[1, 2, 3, 4]
let flatMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].flatMap { $0 } //DEPRECATED(use compactMap): [[1, 2], [3, 4]]
let flatMapCollection3: [Int?] = [[1, nil, 2], [3, 4]].flatMap { $0 } //[Optional(1), nil, Optional(2), Optional(3), Optional(4)]
let flatMapCollection4: [Int] = [1, 2].flatMap { $0 } //DEPRECATED(use compactMap):[1, 2]
//---------------------------------------
//compactMap(one of flatMap before 4.1) - Array, Combine
//removes nil from the input array
//Collection
let compactMapCollection1: [Int] = [1, 2, nil, 4].compactMap { $0 } //[1, 2, 4]
let compactMapCollection2: [[Int]] = [[1, 2], nil, [3, 4]].compactMap { $0 } //[[1, 2], [3, 4]]
[Swift Optional map vs flatMap]
[Swift Functor, Applicative, Monad]

Can somebody explain this change of behavior for flatMap between Swift 3 & 4? [duplicate]

I was examining .lazy for high order functions and have got some interesting compile errors related to flatMap function (and possibly others)
Examples
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print($0)
}
Commenting out a bit
array
.flatMap {
// print("DD")
return $0
}
.forEach {
print("SS")
print($0)
}
And everything works.. even more interesting example
array
.flatMap {
let z = $0
return $0 // Or return z - all is the same "Cannot convert return expression of type 'Int' to return type 'String?'"
}
.forEach {
print("SS")
print($0)
}
What could cause that behavior?
The flatMap(_:) method on Sequence currently (as of Swift 4) has two different meanings:
It can take a transform closure that returns an optional T?, and it will return a [T], filtering out the nil results (this overload is to be renamed to compactMap(_:) in a future version).
public func flatMap<ElementOfResult>(
_ transform: (Element) throws -> ElementOfResult?
) rethrows -> [ElementOfResult]
It can take a transform closure that returns a Sequence, and it will return an array containing the concatenation of all the resulting sequences.
public func flatMap<SegmentOfResult : Sequence>(
_ transform: (Element) throws -> SegmentOfResult
) rethrows -> [SegmentOfResult.Element]
Now, in Swift 4, String became a RangeReplaceableCollection (and therefore a Sequence). So Swift 3 code that did this:
// returns ["foo"], as using the `nil` filtering flatMap, the elements in the closure
// are implicitly promoted to optional strings.
["foo"].flatMap { $0 }
now does this:
// returns ["f", "o", "o"], a [Character], as using the Sequence concatenation flatMap,
// as String is now a Sequence (compiler favours this overload as it avoids the implicit
// conversion from String to String?)
["foo"].flatMap { $0 }
To preserve source compatibility, specialised flatMap overloads were added for strings:
//===----------------------------------------------------------------------===//
// The following overloads of flatMap are carefully crafted to allow the code
// like the following:
// ["hello"].flatMap { $0 }
// return an array of strings without any type context in Swift 3 mode, at the
// same time allowing the following code snippet to compile:
// [0, 1].flatMap { x in
// if String(x) == "foo" { return "bar" } else { return nil }
// }
// Note that the second overload is declared on a more specific protocol.
// See: test/stdlib/StringFlatMap.swift for tests.
extension Sequence {
#_inlineable // FIXME(sil-serialize-all)
#available(swift, obsoleted: 4)
public func flatMap(
_ transform: (Element) throws -> String
) rethrows -> [String] {
return try map(transform)
}
}
extension Collection {
#_inlineable // FIXME(sil-serialize-all)
public func flatMap(
_ transform: (Element) throws -> String?
) rethrows -> [String] {
return try _flatMap(transform)
}
}
Such that the above usage would still return a [String] in Swift 3 compatibility mode, but a [Character] in Swift 4.
So, why does
let array = [1, 2, 3, 4, 5, 6]
array
.flatMap {
print("DD")
return $0 // Cannot convert return expression of type 'Int' to return type 'String?'
}
.forEach {
print("SS")
print($0)
}
tell you that the closure should return a String??
Well, Swift currently doesn't infer parameter and return types for multi-statement closures (see this Q&A for more info). So the flatMap(_:) overloads where the closure returns either a generic T? or a generic S : Sequence aren't eligible to be called without explicit type annotations, as they would require type inference to satisfy the generic placeholders.
Thus, the only overload that is eligible, is the special String source compatibility one, so the compiler is expecting the closure to return a String?.
To fix this, you can explicitly annotate the return type of the closure:
array
.flatMap { i -> Int? in
print("DD")
return i
}
.forEach {
print("SS")
print($0)
}
But if you're not actually using the optional filtering functionality of this flatMap(_:) overload in your real code, you should use map(_:) instead.
flatMap can have different meanings depending on the context. You might tell the compiler more precisely which one you are intending to use.
The two usual purposes to apply flatMap to an array which the compiler can infer is to
flatten nested arrays
let array = [[1, 2, 3, 4, 5, 6], [7, 8, 9]]
let flattened = array.flatMap{$0}
print(flattened) // [1, 2, 3, 4, 5, 6, 7, 8, 9]
map to another type and filter optionals
let array = ["1", "a", "2", "3", "b", "4", "5", "6"]
let flattened = array.flatMap{ Int($0) }
print(flattened) // [1, 2, 3, 4, 5, 6]

Swift 2 Introspection

I am converting some "old" Swift code to Swift 2.0, and I run into an error I cannot get rid of. My function receives an Array of a type (whatever), and returns a new Array of the same type.
This code no longer works in Swift 2.0:
func makePattern1<T>(var list: Array<T>) -> Array<T> {
let theType = list.dynamicType
var result = theType()
for i in 1..<list.count {
result.append(list[i])
result.append(list[i-1])
}
return result
}
giving the error message: "Initializing from a metatype value must reference 'init' explicitly".
Correcting the code with:
var result = theType.init()
gives a "Type of expression is ambiguous without more context" error.
What am I missing?
Just for fun: An alternative solution:
func makePattern2<T>(list: [T]) -> [T] {
return zip(list.dropFirst(), list).flatMap { [$0, $1] }
}
let m = makePattern2([1,2,3,4,5])
print(m) // [2, 1, 3, 2, 4, 3, 5, 4]
Remarks:
list.dropFirst() is a sequence of all but the first array elements.
zip(list.dropFirst(), list) is then a sequence of
(list[$0.i+1], list[$0.i]) tuples.
flatMap { [$0, $1] } combines these tuples to a single array again.
This code is written in Swift 2.1 and does use the map function as you requested in the comments.
func makePattern1<T>(list: [T]) -> [T] {
return list[0..<list.count-1]
.enumerate()
.map { [list[$0.index+1], list[$0.index]] }
.flatten()
.map { $0 as T }
}
Update #1
(thanks Martin R)
func makePattern1<T>(list: [T]) -> [T] {
return list[0..<list.count-1]
.enumerate()
.flatMap { [list[$0.index+1], list[$0.index]] }
}
Update #2
func makePattern1<T>(list: [T]) -> [T] {
return [Int](1..<list.count).flatMap { [list[$0], list[$0-1]] }
}
Test
makePattern1([1,2,3,4,5]) // > [2, 1, 3, 2, 4, 3, 5, 4]