Does Swift have short-circuiting higher-order functions like Any or All?
Asked Answered
A

3

8

I'm aware of Swift's higher-order functions like Map, Filter, Reduce and FlatMap, but I'm not aware of any like 'All' or 'Any' which return a boolean that short-circuit on a positive test while enumerating the results.

For instance, consider you having a collection of 10,000 objects, each with a property called isFulfilled and you want to see if any in that collection have isFulfilled set to false. In C#, you could use myObjects.Any(obj -> !obj.isFulfilled) and when that condition was hit, it would short-circuit the rest of the enumeration and immediately return true.

Is there any such thing in Swift?

Arginine answered 2/6, 2017 at 20:41 Comment(0)
E
6

Sequence (and in particular Collection and Array) has a (short-circuiting) contains(where:) method taking a boolean predicate as argument. For example,

if array.contains(where: { $0 % 2 == 0 })

checks if the array contains any even number.

There is no "all" method, but you can use contains() as well by negating both the predicate and the result. For example,

if !array.contains(where: { $0 % 2 != 0 })

checks if all numbers in the array are even. Of course you can define a custom extension method:

extension Sequence {
    func allSatisfy(_ predicate: (Iterator.Element) -> Bool) -> Bool {
        return !contains(where: { !predicate($0) } )
    }
}

If you want to allow "throwing" predicates in the same way as the contains method then it would be defined as

extension Sequence {
    func allSatisfy(_ predicate: (Iterator.Element) throws -> Bool) rethrows -> Bool {
        return try !contains(where: { try !predicate($0) } )
    }
}

Update: As James Shapiro correctly noticed, an allSatisfy method has been added to the Sequence type in Swift 4.2 (currently in beta), see

(Requires a recent 4.2 developer snapshot.)

Enchantment answered 2/6, 2017 at 21:7 Comment(2)
Note that the allSatisfy function described above will be natively supported for Sequence in Swift 4.2: github.com/apple/swift-evolution/blob/master/proposals/…Welker
@JamesShapiro: You are right, thank you for the feedback.Enchantment
A
3

One other thing that you can do in Swift that is similar to "short circuiting" in this case is to use the lazy property of a collection, which would change your implementation to something like this:

myObjects.lazy.filter({ !$0.isFulfilled }).first != nil

It's not exactly the same thing you're asking for, but might help provide another option when dealing with these higher-order functions. You can read more about lazy in Apple's docs. As of this edit the docs contain the following:

var lazy: LazyCollection> A view onto this collection that provides lazy implementations of normally eager operations, such as map and filter.

var lazy: LazySequence> A sequence containing the same elements as this sequence, but on which some operations, such as map and filter, are implemented lazily.

Agog answered 2/6, 2017 at 21:10 Comment(3)
+1 for the info on the lazy property on Array. I knew about lazy properties that defer their execution until query time, but I didn't know about this specific 'view' into an array. I like it! Thanks!Arginine
your link is dead. Mind updating it? Better yet, mind copying the relevant info here?Arginine
@MarqueIV Updated accordingly.Agog
E
0

If you had all the objects in that array, they should conform to some protocol, which implements the variable isFulfilled... as you can see, you could make these objects confrom to (let's call it fulFilled protocol)... Now you can cast that array into type [FulfilledItem]... Now you can continue as usually

I am pasting code here for your better understanding:

You see, you cannot extend Any or AnyObject, because AnyObject is protocol and cannot be extended (intended by Apple I guess), but you can ,,sublass" the protocol or as you like to call it professionally - Make protocol inheriting from AnyObject...

 protocol FulfilledItem: AnyObject{

    var isFulfilled: Bool {get set}

}

class itemWithTrueValue: FulfilledItem{
    var isFulfilled: Bool = true
}

class itemWithFalseValue: FulfilledItem{
    var isFulfilled: Bool = false
}

var arrayOfFulFilled: [FulfilledItem] = [itemWithFalseValue(),itemWithFalseValue(),itemWithFalseValue(),itemWithFalseValue(),itemWithFalseValue(),itemWithFalseValue()]

  let boolValue =   arrayOfFulFilled.contains(where: {
       $0.isFulfilled == false
    })

Now we've got ourselves a pretty nice looking custom protocol inheriting all Any properties + our beautiful isFulfilled property, which we will handle now as usually...

According to apple docs:

https://developer.apple.com/library/content/documentation/Swift/Conceptual/Swift_Programming_Language/TypeCasting.html#//apple_ref/doc/uid/TP40014097-CH22-ID342

AnyObject is only for reference types (classes), Any is for both value and reference types, so I guess it is prefered to inherit AnyObject...

Now you cast instead AnyObject into Array the protocol Item FulfilledItem and you will have beautiful solution (don't forget every item to conform to that protocol and set the value...)

Wish happy coding :)

Equipage answered 2/6, 2017 at 21:12 Comment(1)
Thanks for the reply, but you're addressing the wrong issue. My question was asking if there was any short-circuiting higher-order functions in Swift. I just used isFulfilled as an arbitrary example. Plus, if we're talking about a homogeneous sequence of a concrete type with an isFulfilled property, you wouldn't create a protocol anyway as you'd just use the property on the object. I didn't state that however so technically you are correct there. Still, ironically though, your answer included what I was after... 'contains(where:) so in a roundabout way you did answer it! :)Arginine

© 2022 - 2024 — McMap. All rights reserved.