Swift closure syntax - swift

I have following code that work:
let obsScan = source.scan(0, accumulator: +)
let obsReduce = source.reduce(0, accumulator: +)
let obs = Observable.zip(obsScan, obsReduce) { scan, reduce in
return "scan - \(scan), reduce - \(reduce)"
}
I want to rewrite it, using auto complete closure syntax, and i ended up with:
let obs = Observable.zip(obsScan, obsReduce, resultSelector: { (scan, reduce) -> _ in
return "scan - \(scan), reduce - \(reduce)"
})
However, that code throw me multiple errors:
Contextual type for closure argument list expects 2 arguments, which
cannot be implicitly ignored Consecutive statements on a line must be
separated by ';' Expected expression
I can't understand why i use tab to autocomplete function with closure, and when i fill arguments i got an errors?
Whole function declared like that:
public static func zip<O1, O2>(_ source1: O1, _ source2: O2, resultSelector: #escaping (O1.E, O2.E) throws -> RxSwift.Observable.E) -> RxSwift.Observable<RxSwift.Observable.E> where O1 : ObservableType, O2 : ObservableType

I'm not sure what are you waiting for. But this should work:
let obs = Observable.zip(obsScan, obsReduce, resultSelector: { scan, reduce in
return "scan - \(scan), reduce - \(reduce)"
})

Related

What is the Swift compiler doing with my return type? Is it somehow casting?

I have a method:
func allRegions() -> [MappedRegion] {
return self.items.lazy.compactMap { item in item.valveAny }.flatMap { valve in valve.regions }
}
I was frankly surprised this worked. I'm doing lazy stuff here, but it's apparently having a lazy sequence that turns into a sequence of MappedRegion be the same.
Then I was doing some poor mans timing and modified the function to read:
func allRegions() -> [MappedRegion] {
let startTime = Date()
let result = self.items.lazy.compactMap { item in item.valveAny }.flatMap { valve in valve.regions }
self.sumRender += (Date() - startTime)
return result
}
But that created an error:
Cannot convert return expression of type 'LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<LazyFilterSequence<LazyMapSequence<LazySequence<[StatusRowItem]>.Elements, ValveAbstract?>>, ValveAbstract>.Elements, [MappedRegion]>>>' (aka 'LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<LazyFilterSequence<LazyMapSequence<Array<StatusRowItem>, Optional<ValveAbstract>>>, ValveAbstract>, Array<MappedRegion>>>>') to return type '[MappedRegion]'
That was initially a surprise. I found that if I specified the return type of result as [MappedRegion] all was happy (e.g. let result:[MappedRegion] = ...).
What is going on here? I get that the original one line function is inferring the result type as [MappedRegion], so I'm probably not getting much benefits from the lazy use. But what confuses me, is that this coercion from a lazy sequence to a fixed array automagically is reminiscent of casting in C, and I thought that Swift didn't do casting?
No, there is no casting going on. There are simply two different flatMap functions being called. LazyMapSequence has two flatMap(_:) functions (well, technically four, but two are deprecated).
In your first code block, this function is inferred (because this version of flatMap has a return type that matches your allRegions function's return type):
func flatMap<SegmentOfResult>(_ transform: (Element) throws -> SegmentOfResult) rethrows -> [SegmentOfResult.Element] where SegmentOfResult : Sequence
And in your second code block, this function is inferred (because there is no type annotation on your local variable that's forcing it to choose the above version of flatMap):
func flatMap<SegmentOfResult>(_ transform: #escaping (Element) -> SegmentOfResult) -> LazySequence<FlattenSequence<LazyMapSequence<LazyMapSequence<Base, Element>, SegmentOfResult>>> where SegmentOfResult : Sequence

Is there any overhead in wrapping a function in a closure to pass it as argument

In swift, you can pass functions as parameters to functions accepting closures. This is particularly useful to avoid syntactically polluting your code when using operators. For instance, you can write a sum as follows:
let values = 0 ..< 10
let sum = values.reduce(0, +)
Unfortunately, overloaded functions can lead to ambiguous situations when Swift’s inference is unable to determine the type of the expected closure from other arguments. Consider the code below for instance. The last line does not compile because Swift cannot decide what “version” of + I am referring to.
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// The following line cannot compile.
let x = castAndCombine((1, 2), with: +)
Unfortunately, there isn’t (or at least I am not aware of) any way to specify which + I mean. Nonetheless, I came up with two solutions to this problem:
Add a parameter to the function to disambiguate the situation:
func castAndCombine<T, U>(_ pair: (Any, Any), toType: T.Type, with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), toType: Int.self, with: +)
Leave the function’s signature unchanged and use a closure with explicit type annotations:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
// ...
}
let x = castAndCombine((1, 2), with: { (a: Int, b: Int) in a + b })
I personally dislike the first solution, as I feel it is not aesthetic and unnatural to use. However, I wonder if the second one adds any performance overhead, due to the creation of a closure that essentially wraps a single function, without adding any behavior.
Does anyone know if this performance overhead does actually exist and/or is significant to any extent?
There should not be any overhead if you compile with optimizations, as the compiler will most likely inline your closure.
You can verify this assumption with your first solution (as it supports both styles) by comparing the LLVM code Swift writes. LLVM is an intermediate representation used by the compiler right before creating actual machine code.
Write one file using the operator directly, i.e.:
let x = castAndCombine((1, 2), toType: Int.self, with: +)
Write a second file using the closure, i.e.:
let x = castAndCombine((1, 2), toType: Int.self, with: { (a: Int, b: Int) in a + b })
Now compile both with optimizations, asking Swift's compiler to produce the LLVM IR. Assuming your files are named main1.swift and main2.swift, you can run the following:
swift -O -emit-ir main1.swift 1>main1.ll
swift -O -emit-ir main2.swift 1>main2.ll
Both produced files should be identical.
diff main1.ll main2.ll
# No output
Note that the solutions suggested in the comments do not add any performance overhead either, as statically guaranteed casts do not cost any operation.
Instead of creating a closure to disambiguate the type, you can cast + to the desired type:
func castAndCombine<T, U>(_ pair: (Any, Any), with fn: (T, T) -> U) -> U? {
guard let first = pair.0 as? T, let second = pair.1 as? T
else { return nil }
return fn(first, second)
}
// Add two Ints by concatenating them as Strings
func +(_ lhs: Int, _ rhs: Int) -> String {
return "\(lhs)\(rhs)"
}
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> String) {
print(x)
}
12
if let x = castAndCombine((1, 2), with: (+) as (Int, Int) -> Int) {
print(x)
}
3

Confusion over .map transform closure

The code below compiles and runs OK, and seems to indicate that the closure and String.init(describing:) functions are completely equivalent in their signature, since .map method happily takes both of them.
let someDict: [String: String] = [
"string1" : "Hello",
"string2" : "Bye",
]
//One way to call .map
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
//Another way to call .map
someDict.map(String.init(describing:))
But how is it possible to place into .map a String.init(describing:) function which is a function of only 1 argument, while .map expects a function of 2 arguments? Or am i misunderstanding something here..
Btw, checking the documentation shows that it really does expect a function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Btw, checking the documentation shows that it really does expect a
function of 2 arguments:
transform: ((key: String, value: String)) throws -> T
Actually, no. Notice the extra parentheses (). It shows that it expects a function that takes one argument which is a tuple containing two elements.
Consider this example:
// function foo takes two arguments
func foo(_ a: Int, _ b: Int) -> Int {
return a + b
}
// function bar takes one tuple with two elements
func bar(_ a: (Int, Int)) -> Int {
return a.0 + a.1
}
let f1 = foo
print(type(of: f1)) // (Int, Int) -> Int
let f2 = bar
print(type(of: f2)) // ((Int, Int)) -> Int
So, the extra parentheses tell us that map is expecting one argument that is a tuple containing two elements.
The closure passed to map always operates on a single element from the sequence at a time. That element can be a tuple such as your case, and then your closure can deconstruct that tuple into multiple values.
Consider this example:
// tup is a tuple containing 3 values
let tup = (1, true, "hello")
// deconstruct the tuple through assignment
let (x, y, z) = tup
print(x) // 1
print(y) // true
print(z) // hello
So in this example:
var closure = { (key: String, value: String) -> String in
return "The key is \(key), the value is \(value)"
}
someDict.map(closure)
map's closure is given a tuple of the form (key: String, value: String) and the closure is deconstructing that into key and value just as the let did above.
In this example:
someDict.map(String.init(describing:))
which is equivalent to:
someDict.map({ String(describing: $0) })
map is taking the whole tuple and passing it to String(describing:).

Loop works, get error when trying map-reduce

I'm new to map-reduce and wanted to play around with it a bit. I hope this question isn't too stupid.
I have this code working:
var str = "Geometry add to map: "
for element in geometryToAdd {
str.append(element.toString())
}
print(str)
Now I wanted to play around with map-reduce since I learned it recently. I rewrote it as this:
print(geometryToAdd.reduce("Geometry add to map: ", {$0.append($1.toString())}))
This gives me an error error: MyPlayground.playground:127:57: error: type of expression is ambiguous without more context. What do I do wrong?
var geometryToAdd: Array<Geometry> = []
and the class Geometry has a toString function.
Thanks for any help.
There are two similar methods:
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
func reduce<Result>(into initialResult: Result, _ updateAccumulatingResult: (inout Result, Element) throws -> ()) rethrows -> Result
You are using the first version, where $0 is immutable and the closure
must return the accumulated value. This does not compile because
append() modifies its receiver.
Using the second version makes it compile: Here $0 is mutable and
the closure updates $0 with the accumulated value.
print(geometryToAdd.reduce(into: "Geometry add to map: ", {$0.append($1.toString())}))
Make it less ambiguous :
print(geometryToAdd.reduce("Geometry add to map: ", {
$0 + $1.toString()
}))
The error comes from the fact that you can only append() to a variable sequence : $0 is an immutable String. In the loop, str was mutable : a var, not a let.
Have a look at the signature of reduce
func reduce<Result>(_ initialResult: Result, _ nextPartialResult: (Result, Element) throws -> Result) rethrows -> Result
nextPartialResult is a function/closure that takes two arguments and gives a result. The arguments of this function are immutable, they are not inout parameters. Only inout parameters can be modified.
Find out more on function parameter immutability here :
Function parameters are constants by default. Trying to change the
value of a function parameter from within the body of that function
results in a compile-time error.
You would be better off using joined(separator:) instead of reduce. It has better performance, and lets you put in a separator, if you wish:
print("Geometry add to map: \(geometryToAdd.map(String.init).joined(separator: "")")

Swift: Reduce with closure

Code:
var treasures: [Treasure] = []
treasures = [treasureA, treasureB, treasureC, treasureD, treasureE]
let rectToDisplay = self.treasures.reduce(MKMapRectNull) {
(mapRect: MKMapRect, treasure: Treasure) -> MKMapRect in
// 2
let treasurePointRect = MKMapRect(origin: treasure.location.mapPoint, size: MKMapSize(width: 0, height: 0))
// 3
return MKMapRectUnion(mapRect, treasurePointRect)
}
In the code above, we are running the reduce function on treasures array, two parameters are passed in the closure: (mapRect: MKMapRect, treasure: Treasure). How does the closure know to that the second parameter will be the element from the treasures array and the first parameter will be result of the what this closure returns?
Is this something by default that the second parameter passed in the closure will be the element from the array that's executing the reduce function?
Swift's array class has a definition of reduce that most likely looks something like this:
func reduce<T>(initial: T, fn: (T, T) -> T) -> T {
var val = initial
for e in self {
val = fn(val, e)
}
return e
}
That is to say, the definition of reduce dictates the order in which parameters are passed to the closure you provide.
Note that the actual definition of Swift's reduce is more complicated than the one I provided above, but the example above is the basic gist.
If you look at the definition of reduce:
func reduce<S : SequenceType, U>(sequence: S, initial: U, combine: #noescape (U, S.Generator.Element) -> U) -> U
The first parameter of the closure is the result and the second is element of your sequence.