compactMap on sequence() not lazy? - swift

Every once in a while I have to walk up the responder chain to reach an instance of a known class. (Just accept this for purposes of the question.) I've been doing this with a while loop, but it occurred to me that it would be cooler to use sequence(), which can express the responder chain itself neatly like this:
let chain = sequence(first: someView as UIResponder) {$0.next}
That's brilliant because so far we haven't actually done any walking; the sequence is lazy and the anonymous function won't be executed until we start asking for elements. To prove that, let me instrument that code with a print statement:
let chain = sequence(first: someView as UIResponder) {r in print(r); return r.next}
Okay, so let's say I'm looking for the first ViewController instance in the chain. I can find it like this:
if let vc = (chain.first {$0 is ViewController}) as? ViewController {
print(vc)
}
The printout shows that laziness is maintained: we walked up the responder chain until we got to the ViewController and stopped. Perfect! Inside the curly braces, vc is typed as ViewController and we're off to the races.
It will not have escaped your attention, however, that that's ugly. I'm both testing and casting. Is there a way I can just cast without testing and still end up with a ViewController?
This is elegant and it works fine:
for case let vc as ViewController in chain {
print(vc)
break
}
That's lovely and laziness is maintained — but I have to remember to say break at the end, which sort of ruins everything.
Okay, so I was really hopeful when I thought of this:
if let vc = (chain.compactMap{ $0 as? ViewController }.first) {
print(vc)
}
It works in the sense that it compiles and gets the right answer and looks nice, but I've lost laziness. The entire chain is being traversed. Does compactMap lose laziness? Is there a way to get it back? (Or is there some other elegant way that has completely escaped me?)

The issue is not compactMap, per se. There are two issues:
If you want the sequence to call compactMap lazily, you need to use lazy.
It would appear that first is preventing the lazy behavior. If you use first(where:), though, you do enjoy the lazy behavior.
Thus, while somewhat inelegant, the following achieves what you’re looking for:
if let vc = (chain.lazy.compactMap { $0 as? ViewController }.first { _ in true } ) {
...
}
Or, as you say, you can implement first (or lazyFirst) on Sequence:
extension Sequence {
var first: Element? {
return first { _ in true }
}
}
And then this more simplified rendition now is still lazy:
if let vc = chain.lazy.compactMap({ $0 as? ViewController }).first {
...
}

Related

Swift Functional with Array and a Guard ... Possible?

Trying to make this common swift pattern more functional.
for object in objects.allObjects {
guard let _object = object as? SomeTypeOfObject else { continue }
_object.subObject(subObject, objectStateChanged: changedState)
}
I'd love to use something like "any" (instead of map) to get rid of the for loop - like in Kotlin - but that dose not seem to be possible in Swift.
Trying to figure out a way to include the guard in the functional representation of this block.
Feels like I'm missing something. Am I?
Thanks
A common functional pattern is to compactMap and then call the method with side-effect in a forEach. This eliminates the guard entirely:
objects.allObjects // Add `.lazy` if you don’t want to build the intermediary array
.compactMap { $0 as? SomeTypeOfObject }
.forEach { $0.subObject(subObject, objectStateChanged: changedState) }

Unwrapping optionals in Apple's game code project template

In the small sample of Apple's code which is created when you make a new game project in Xcode, the GameScene has several functions which use an unwrapping pattern I've not come across, & I fail to see the point of it -
if let n = self.spinnyNode?.copy() as! SKShapeNode? { ...code... }
Two questions - (1) is this dangerous, & (2) why not use the more common pattern -
if let n = self.spinnyNode?.copy() as? SKShapeNode { ...code... }
I can find nothing relating to this on SO or Google...
Lets break down the original line of code:
if let n = self.spinnyNode?.copy() as! SKShapeNode? { ...code... }
self.spinnyNode is declared as an SKShapeNode?. In other words, it's an optional SKShapeNode.
SKShapeNode inherits from NSObject which provides the copy method. The copy method is declared to return Any.
self.spinnyNode?.copy() will return an Any? with a value of nil if self.spinnyNode is nil or it will return a non-nil Any? if it's not.
So the compiler thinks that self.spinnyNode?.copy() will return an Any?. But we know it will really return a SKShapeNode?. Since we know what it will really be we can safely use as!. And since we know it will return a SKShapeNode?, we use as! SKShapeNode?.
At this point we could have:
let n = self.spinnyNode?.copy() as! SKShapeNode?
where n will be a SKShapeNode?.
Since we now want to safely unwrap the result, we add the if in front of the let:
if let n = self.spinnyNode?.copy() as! SKShapeNode? { ...code... }
and inside the block we know that n is a non-nil SKShapeNode.
With all of that, let's answer your questions:
Is this dangerous? No, not in this case since we know for sure that self.spinnyNode?.copy() will always result in a SKShapeNode? we can safely use as! to cast it to SKShapeNode?.
Why not use the more common as? SKShapeNode pattern? We could but that pattern is used when you don't know for sure that the result is actually a SKShapeNode.

Swift basic expression

I'm very new to swift, but proficient in other languages like Java, JavaScript, C, ... I'm lost with Swift syntax when it comes to create expressions. Look at this basic example where I just try to find out if one string is contained into another by calling String.rangeOfString that returns an Optional Range (Range?)
This works as expected:
let LEXEMA:String="http://"
let longUrl:String="http://badgirls.many/picture.png"
let range=longUrl.rangeOfString(LEXEMA);
if (range? != nil) {
// blah
}
Now I'm trying to combine the expression inside the if, something like:
if (longUrl.rangeOfString(LEXEMA)? !=nil) {
// blah
}
But I always get syntax errors, the above yields a "Expected Separator" and can't understand why. Done some more tests:
if (absolutePath.rangeOfString(URL_LEXEMA) !=nil) { }
Expected Separator before "!"
if absolutePath.rangeOfString(URL_LEXEMA) !=nil { }
Braced block of statements is an unused closure
What am I doing wrong?
If you’re coming from other like Java, you might be thinking of optionals like pointers/references, and so used to equating them to nil and if non-nil, using them. But this is probably going to lead to more confusion. Instead, think of them like a container for a possible result, that you need to unwrap to use. if let combines the test and unwrapping operation.
With this in mind, here’s how you could adapt your code:
let LEXEMA: String="http://"
let longUrl: String="http://badgirls.many/picture.png"
if let range = longUrl.rangeOfString(LEXEMA) {
// use range, which will be the unwrapped non-optional range
}
else {
// no such range, perhaps log an error if this shouldn’t happen
}
Note, that ? suffixing behaviour you were using changes in Swift 1.2 so even the code in your question that compiles in 1.1 won’t in 1.2.
It’s possible that sometimes you are whether there was a value returned, but you don’t actually need that value, just to know it wasn’t nil. In that case, you can compare the value to nil without the let:
if longUrl.rangeOfString(LEXEMA) != nil {
// there was a value, but you don't care what that value was
}
That said, the above is probably better expressed as:
if longUrl.hasPrefix(LEXEMA) { }
For starters:
You don't need parenthesis with if statements unless you have nested parenthetical subexpressions that require it.
You don't need to specify the type on the left side of the = of a let or var declaration if Swift can figure it out from the right side of the =. Very often Swift can figure it out, and you can tell that Swift can figure it out, so you can avoid that redundant clutter.
You do need to specify the type if Swift cannot figure out the type from
the right side. Example:
For example, consider the following lines:
let LEXEMA = "http://"
let longUrl = "http://badgirls.many/picture.png"
Swift can figure out that they're strings.
Similarly for this function or class that returns a UIView:
var myView = ViewReturningClassOrFunc()
Consider this:
#IBOutlet var myView : UIView!
In the above line, Swift cannot figure out ahead of time it will be assigned a UIView, so you have to provide the type. By providing a ! at the end you've made it an implicitly unwrapped optional. That means, like ?, you're indicating that it can be nil, but that you are confident it will never be nil at the time you access it, so Swift won't require you to put a ! after it when you reference it. That trick is a time saver and big convenience.
You should NOT add the ? to the line:
if (longUrl.rangeOfString(URL_LEXEMA) !=nil) {
As another answer pointed out, you're missing the let.
if let longUrl.rangeOfString(URL_LEXEMA) {
println("What do I win? :-)")
}
swift is case sensitive language. you need to check about whitespaces as well
if longUrl.rangeOfString(LEXEMA) != nil {
//your condition
}
there should be space between statement != nil
Just add a space between != and nil like:
if longUrl.rangeOfString(LEXEMA) != nil {
// blah
}
I tested your code in playground, an error of Expected ',' separator reported.
And do not forget the rules that 1s and 0s and Airspeed Velocity said.

Mixing for-in and if-let in Swift

Can I merge a for-in and if-let in one statement?
for item in array {
if let f = item as? NSDictionary {
result.addObject(newFile(f))
}
}
array is made by a JSON, so I don't know if each item is a NSDictionary or not. I have to check.
I was looking for something like this:
for item as? NSDictionary in array {
// code
}
Like Python or Ruby.
#nickfalk is on the right track, but we can do better. His result unfortunately returns [AnyObject], which you can't then call newFile with (I assume). But that's ok, we can get the rest of the way pretty easily.
What you want is partial map. That is to say, you want to map some (but possibly not all) of the elements of one list to another list (from AnyObject to File, if we can). So there must be some rule for choosing, and some rule for mapping. Optional let's us combine those. Let's call the function that does that f. Then its type is:
f: T->U?
So there's some magic function that will possibly convert T to U. We want to map with that. Sounds easy:
extension Array {
func partialMap<U>(f: T->U?) -> [U] {
var result = [U]()
for x in self {
if let u = f(x) {
result.append(u)
}
}
return result
}
}
So now we've hidden all the nasty mutation and var and whatnot down deep where we don't have to look at it. We have a function that takes a mapping function from "something" to "maybe something else" and returns a list of "something elses that we could map."
Now everything is nice and immutable and reusable:
let result = array.partialMap { ($0 as? NSDictionary).map(newFile) }
Whoa there. What's that map in the middle? Well, as? returns NSDictionary?. When you map an optional, then if the optional is None, it returns None, otherwise it applies the function to the value and wraps it in Some. So this whole thing takes AnyObject and returns File? just like we wanted. One partialMap later we have our answer.
I would probably just go for something like:
let result = array.filter() { $0 is NSDictionary }
If you need result to be an NSDictionary array, you can just cast it:
let result = array.filter() { $0 is NSDictionary } as [NSDictionary]
If your goal is to reduce an NSArray to an array only containing NSDictionary filter is a very powerful tool. Create the appropriate filtering function:
func filterForNSDictionary(object: AnyObject) -> Bool{
return object.isKindOfClass(NSDictionary)
}
Then simply pass in you array and function to the filter function
let result = filter(array, filterForNSDictionary)
As #RobNapier points out my solution above will end up with a result array being of the type [AnyObject] this can of course easily be remedied:
let result = filter(array, filterForNSDictionary) as [NSDictionary]
This could be considered risky, if you force the array to be of the wrong type. as [NSString] (for instance9 would most likely blow up in your face down the line...
Rob's solution being pure awesome cleverness of course and #MattGibson delivering the perfect shorthand, while exposing me as an absolute beginner in this field.

Can you continue a loop if optional downcasting fails in Swift?

It's a very common idiom to continue a loop if some condition fails on an element.
Say we want to do something to all subviews of a certain type (and, for some reason, don't want to duck type things). Ideally, we would write:
for view in self.subviews as [NSView] { // cast required in beta 6
if (let specificView = view as? SpecificView) == nil { // <- Error here
continue
}
// Do things at a sensible indentation level
}
The above code fails with 'Pattern variable binding cannot appear in an expression', as in this question.
However, this seems like such a common pattern that there has to be a way to do it in Swift. Am I missing something?
EDIT: Now that I think about it, this appears to fall afoul of the scoping rules for if let statements, which only scope the variable to the inner block.
With that in mind, I'd like to broaden the question a little: how do people apply this pattern generally in Swift?
This is a somewhat common pattern, but it is not a good pattern. I've seen it inject bugs into ObjC projects. It assumes too much about the view hierarchy, and winds up being fragile when that changes (such as when someone injects an extra view you weren't expecting in order to manage rotations; true story). The better pattern is to maintain a property that points to your SpecificView (or views) that you want to track. Downcasting in general is something to be avoided, not optimized.
That said, it is not a terrible pattern, and sometimes it is a very useful pattern. So how might you handle it?
let specifics = self.subviews
.filter { $0 is SpecificView }
.map { $0 as SpecificView }
for view in specifics { ... }
That's kind of a common pattern, so maybe we can genericize it?
extension Array {
func filterByClass<T>(c: T.Type) -> [T] {
return self.filter { $0 is T }.map { $0 as T }
}
}
for view in self.subviews.filterByClass(SpecificView) { ... }
That said, I think this approach should be avoided wherever possible rather than excessively simplified.