Saranjith
Saranjith

Reputation: 11577

How to extend arithmetic operators to support optional values as well - Swift

In the below example,

let value1: Int? = 23
let value2: Int = 20

let answer = value1 + value2 // Compiler warning that + operator cannot be applied to Int? and Int

So I would have to change the code to

if let value1 = value1 {
    let answer = value1 + value2 
}

How to create an extension for + that supports Optional values as well? In that case it should give nil as output.

What if the operation has multiple operands?

let value1: Int? = 2

let answer = value1 + 3.0

Upvotes: 5

Views: 1075

Answers (4)

Cristik
Cristik

Reputation: 32870

Optionals don't make much sense to be added, a nil value usually represents something went wrong along the way and a value could not be retrieved. It's better to be explicit, and to write the if-let statements. This way you can handle the cases where you end up with nil values.

But if you want to ignore the nils, then you can use the nil coalescing operator:

let value1: Int? = 23
let value2: Int = 20

let answer = (value1 ?? 0) + value2 

You are still explicit about the unwrap, but you also have a fallback route in case you want to ignore the nil value. And, maybe even more important, the code transmits its scope in a clear manner. If someone later on stumbles upon your code it will be clear to them what the code does and how it recovers from unexpected situations (nil is an unexpected value in regards to the addition)

Upvotes: -1

Sajjon
Sajjon

Reputation: 9907

Only pasting solution for addition, but other operators will work analogously (but mind subtract and division, since they are not commutative)

Three reasonable solutions

  1. Global function(s)
  2. Extend existential* (conforming to AdditiveArithmetic) to some new protocol e.g. AdditiveArithmeticOptional
  3. Extend Optional

* Note: you can read about existentials here, e.g. protocol isn't an existential, but a concrete type, e.g. a struct is.

1 Global function(s)

See @Sweepers answer

Note: a global function is a function not implemented on a type (protocol or existential). Swift's zip function is an example

2 Extend existentials to new protocol


public protocol AdditiveArithmeticOptional: AdditiveArithmetic {
    static func + (lhs: Self, rhs: Self?) -> Self
}

public extension AdditiveArithmeticOptional {

    static func + (lhs: Self, rhs: Self?) -> Self {
        guard let value = rhs else { return lhs }
        return value + lhs
    }

    static func + (lhs: Self?, rhs: Self) -> Self {
        rhs + lhs
    }
}
extension Int8: AdditiveArithmeticOptional {}
extension Int16: AdditiveArithmeticOptional {}
extension Int32: AdditiveArithmeticOptional {}
extension Int64: AdditiveArithmeticOptional {}
extension Int: AdditiveArithmeticOptional {} // same as `Int64` on 64 bit system, same as `Int32` on 32 bit system

extension UInt8: AdditiveArithmeticOptional {}
extension UInt16: AdditiveArithmeticOptional {}
extension UInt32: AdditiveArithmeticOptional {}
extension UInt64: AdditiveArithmeticOptional {}
extension UInt: AdditiveArithmeticOptional {} // same as `UInt64` on 64 bit system, same as `UInt32` on 32 bit system


3 extend Optional

extension Optional where Wrapped: AdditiveArithmetic {
    static func + <I>(optional: Self, increment: I) -> I where I: AdditiveArithmetic & ExpressibleByIntegerLiteral, I.IntegerLiteralType == Wrapped {
        guard let value = optional else { return increment }
        let base = I.init(integerLiteral: value)
        return base + increment
    }

    static func + <I>(increment: I, optional: Self) -> I where I: AdditiveArithmetic & ExpressibleByIntegerLiteral, I.IntegerLiteralType == Wrapped {
        optional + increment
    }
}

Upvotes: 2

Sweeper
Sweeper

Reputation: 273380

You just have to find the right protocol type to constrain the generic types, really. After that the implementation is trivial:

// plus and minus is supported by AdditiveArithmetic
func +<T: AdditiveArithmetic>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { x in rhs.map { y in x + y } }
    /* the above is just a more "functional" way of writing 
    if let x = lhs, let y = rhs {
        return x + y
    } else {
        return nil
    }
    */
}

func -<T: AdditiveArithmetic>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { x in rhs.map { y in x - y } }
}

// times is supported by Numeric
func *<T: Numeric>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { x in rhs.map { y in x * y } }
}

// divide is not supported by a single protocol AFAIK
func /<T: BinaryInteger>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { x in rhs.map { y in x / y } }
}

func /<T: FloatingPoint>(lhs: T?, rhs: T?) -> T? {
    return lhs.flatMap { x in rhs.map { y in x / y } }
}

To make value1 + 3.0 work, you'd have to do something like this:

func +<T: BinaryInteger, U: FloatingPoint>(lhs: T?, rhs: U?) -> U? {
    return lhs.flatMap { x in rhs.map { y in U(x) + y } }
}

But it's usually not a good idea to go against the restrictions put in place. I don't recommend this.

Upvotes: 4

Frankenstein
Frankenstein

Reputation: 16361

You could create your own custom operator function if you have multiple scenarios where you require arithmetic operations between optionals as follows:

func + (lhs: Int?, rhs: Int?) -> Int {
    (lhs ?? 0) + (rhs ?? 0)
}
func + (lhs: Int?, rhs: Int) -> Int {
    (lhs ?? 0) + rhs
}
func + (lhs: Int, rhs: Int?) -> Int {
    lhs + (rhs ?? 0)
}

Note: Add return keyword if you are using Swift 5 or below.

Update: Upon further investigation and inspiration from the answer of @sweeper following solution seemed more elegant.

func + <T: AdditiveArithmetic>(lhs: T?, rhs: T?) -> T {
    (lhs ?? .zero) + (rhs ?? .zero)
}

or if you need a nil when operation was not successful

func + <T: AdditiveArithmetic>(lhs: T?, rhs: T?) -> T? {
    lhs.flatMap { lhs in rhs.flatMap { lhs + $0 }}
}

Upvotes: 1

Related Questions