How would you test an arbitrary value for “boolness”?

How would you write this?

SomeValueProtocol is a protocol that covers Bool, all Swift integer types, as well as non-related types. I would like to do something like this, but I am having trouble with the integer part.

func boolValue(_ someValue: SomeValueProtocol) -> Bool? {
  if let value = someValue as? Bool {
    return value
  } else if let value = someValue as? BinaryInteger {
    // How would I write this in a way that compiles?
    return value == 0 ? false : true
  } else {
    return nil
  }
}

I know how to write this in a redundant way, trying to cast someValue into all possible Swift integer types (Int, Int8, Int16 etc with their unsigned counterparts) but there must be a better way.

if you extend BinaryInteger in this manner, it will likely do what you want:

extension BinaryInteger {
  var boolValue: Bool {
    self == Self.zero ? false : true
  }
}
// ...
else if let value = someValue as? (any BinaryInteger) {
    return value.boolValue
// ...

but to address more of the 'what's going on here' aspect of this question, in this code:

the value variable is of type any BinaryInteger (a.k.a, a 'protocol existential' – see here & here for more info), and as such, trying to compare it to the literal 0 is not going to work. the existential value needs to be 'opened' and its contents compared to the zero value of whatever the appropriate BinaryInteger type is. this can be achieved in a couple ways – either via a protocol extension, as outlined above, or by using a generic function that will implicitly 'open' the existential when it is passed as a parameter, e.g.

func boolValue<T: BinaryInteger>(_ value: T) {
  value == T.zero ? false : true
}

one additional strategy you might consider is to add the requirement of having a derived boolean value into your SomeValueProtocol. then each conforming type would provide whatever the appropriate implementation is.

2 Likes

Are you doing this already?

extension Int: SomeValueProtocol {}
extension Int8: SomeValueProtocol {}
...

Are you doing this already?

Yes. Well, I am not, but Apple whose protocol it is, has done that. SomeValueProtocol is actually CloudKit’s CKRecordValue which indeed has these kinds of conformances declared in CloudKit:

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 3.0, *)
extension Bool : CKRecordValueProtocol {
}

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 3.0, *)
extension Int : CKRecordValueProtocol {
}

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 3.0, *)
extension UInt : CKRecordValueProtocol {
}

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 3.0, *)
extension Int8 : CKRecordValueProtocol {
}

@available(macOS 10.10, iOS 8.0, tvOS 9.0, watchOS 3.0, *)
extension UInt8 : CKRecordValueProtocol {
}

etc.

Use BinaryInteger protocol for all Swift integer types.

extension BinaryInteger {
  var boolValue: Bool {
    self == Self.zero ? false : true
  }
}
// ...
else if let value = someValue as? BinaryInteger {
    return value.boolValue
// ...

Thank you. This does exactly what I need, and the rest of your post is clear about what’s going on.

The missing piece of my knowledge was that BinaryInteger inherits from AdditiveArithmetic which providers .zero type property, that’s exactly what I need to use here.

Just a small thing, your code above casts the value as if let value = someValue as? BinaryInteger. I need to use as? any BinaryInteger to get it to compile, otherwise the compiler complains: Use of protocol 'BinaryInteger' as a type must be written 'any BinaryInteger'

apologies for the confusion, this was an oversight – the sample code i offered also does not compile for the same reason (i'll update it). if you're curious about the context surrounding that error, you can find some relevant information in SE-335.

3 Likes