32

I am trying to reduce an array of Bools by applying the logical operator OR (||) using the following code, however I get an error:

func reduceBools(values: [Bool]) -> Bool {
    return values.reduce(false, combine: ||)
}

Ambiguous reference to member '||'

Analogously for integers the code works like a charm.

func reduceInts(values: [Int]) -> Int {
    return values.reduce(0, combine: +)
}

I was able to make it work by adding a || function (code below) or using a { $0 || $1 } closure but I dislike these approaches and I would prefer simply passing the operator.

func ||(lhs: Bool, rhs: Bool) -> Bool {
    return lhs || rhs
}

The same thing happens for the logical AND (&&) operator.

How can I make it work without using the hack above?

えるまる
  • 2,409
  • 3
  • 24
  • 44
fpg1503
  • 7,492
  • 6
  • 29
  • 49

6 Answers6

43

As an alternative, you could use the following approach

// ||
func reduceBoolsOr(values: [Bool]) -> Bool {
    return values.contains(true)
}

// &&
func reduceBoolsAnd(values: [Bool]) -> Bool {
    return !values.contains(false)
}

Note that .reduce comes with an overhead. If the end result is the importance of your question (rather than enquiring above the unexpected behaviour of || and && operators in this context), then perhaps the pragmatic approach above can be of help, even if it doesn't really reduce the array, however producing the same result due to the simple nature of the boolean type.

Community
  • 1
  • 1
dfrib
  • 70,367
  • 12
  • 127
  • 192
  • 2
    I'd argue that using contains is even better in this case, because you're describing the result you want to get, rather than how to calculate it. It's even more declarative than the usual functional approach. :) – NiñoScript Aug 30 '16 at 18:26
14

Swift 4.2+ / Xcode 10.0+

In modern versions of Swift there is allSatisfy function, which checks all elements for satisfying some rule.


In OP's case:

values.allSatisfy { $0 }

UPD:

To make this work for OR, do

!values.allSatisfy{!$0}

Thanks to Andy Weinstein

えるまる
  • 2,409
  • 3
  • 24
  • 44
8

Following approach will work

values.reduce(false) { $0 || $1 }
ldindu
  • 4,270
  • 2
  • 20
  • 24
2

Ambiguous reference to member '||' means, that there are more than one possible candidates, from which compiler is not able to choose. In your case those are

public func ||<T : BooleanType, U : BooleanType>(lhs: T, @autoclosure rhs: () throws -> U) rethrows -> Bool

and

public func ||<T : BooleanType>(lhs: T, @autoclosure rhs: () throws -> Bool) rethrows -> Bool

probably your 'hack' using a { $0 || $1 } is the best solutions here.

user3441734
  • 16,722
  • 2
  • 40
  • 59
  • It looks like the only reason for the second one is that the first one cannot be `@_transparent` due to a bug. The craziest thing is that their implementations are identical. – fpg1503 Jan 10 '16 at 04:43
2

This happens because of Swifts closure semantics. It takes your arguments and applies function to them, omitting argument names.

protocol Numeric {
    ...
    public static func +(lhs: Self, rhs: Self) -> Self
    ...
}

In example with Ints, you would pass (Int, Int) into a closure, and + function in Numeric protocol expects exactly two ints to sum them.

Thats why code like below works just fine

[1, 2, 3, 4].reduce(0, +)

Because you just took 2 ints, and applied function, which takes just two ints. If you write your own function, which would take just two argument, it would work as well.

func myOwnAwesomeFunc<T: Numeric>(a: T, b: T) -> T { in
    return 1 // production ready
}

[1, 2, 3, 4].reduce(0, myOwnAwesomeFunc) // prints 1

Good so far. But why can't we write

[true, false, true].reduce(false, ||) // yields Cannot invoke 'reduce' 
// with an argument list of type 
// '(Bool, (Bool, @autoclosure () throws -> Bool) throws -> Bool)'

That's because this operator takes bool and a closure, which returns bool. Not bool, closure! But if it is like this, why aren't we writing true || { false }() ? Thats because of @autoclosure, which takes care of curly braces for us.

Main question, why is it implemented this way, so we can't use Swifts awesome short-hand closure syntax with booleans? Idk

Alex V.
  • 29
  • 4
0

Here's another approach, I modified the reduceBools function to take the operator as a parameter -

typealias LogicalOperator = ((Bool, @autoclosure () throws -> Bool) throws -> Bool)

func reduceBools(values: [Bool], combine: LogicalOperator) -> Bool {
    var started: Bool = false
    return values.reduce(into: true, { (result, value) in
        result = started ? try! combine(result, value) : value // obviously up to you how you'd handle the try/catch
        started = true
    })
}

let bools = [true, false, false, true]

let result1 = self.reduceBools(values: bools, combine: ||)
print(result1) // prints true

let result2 = self.reduceBools(values: bools, combine: &&)
print(result2) // prints false

Or it could be more useful as an extension of Sequence -

extension Sequence where Element == Bool {

    func reduce(_ combine: LogicalOperator) -> Bool {
        var started: Bool = false
        return self.reduce(into: true, { (result, value) in
            result = started ? try! combine(result, value) : value
            started = true
        })
    }
}

print(bools.reduce(||)) // prints true
SomaMan
  • 4,127
  • 1
  • 34
  • 45