Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Generic way to do math on protocol extensions

Goal

I want to extend basic types like Int, Double, Float... with more flexible properties and make it presentable in a chart on my app. For example, I made a chart draw that is suitable only for displaying Intbut cannot really display Float. I want to make sure when I pass arguments to this view it will display correctly.

Solution

So I made a protocol (for this example made it like this):

protocol SimplyChartable { 
        static func max(_ dataSet: [SimplyChartable]) -> SimplyChartable
}

And then make an extension for some types:

extension Int: SimplyChartable { }
extension Double: SimplyChartable { } 
extension Float: SimplyChartable { } 

and so on ...

Problem

This will be all numeric types, and whenever I pass it as numeric types to a func I need to extend all extension like this:

public static func max(_ dataSet: [SimplyChartable]) -> SimplyChartable { 
    return (dataSet as? [Int])?.max() ?? 0
}

But for Double func will be identical.

So for min I will end up with similar function, the same for divide, adding , some other math... There is a way to write it once and reuse for every type that extends this protocol?

I found out that:

    let dataType = type(of: maxValue) /* where `maxValue` is SimplyChartable*/

Will return original type as rawValue. But output of a method type(of is a Metatype and I cannot return it from function and then add two values of this type. So for example this code will not work:

let val1 = SimplyChartable(4)
let val2 = SimplyChartable(2)
let sum = val1 + val2

And how to make it work not ending up with 3 functions like this:

let val1 = SimplyChartable(4)
let val2 = SimplyChartable(2)
let sum = (val1 as! Int) + (val2 as! Int)
like image 792
Max Avatar asked Aug 13 '17 21:08

Max


4 Answers

Since they all numeric types why don't you use Comparable?

extension SimplyChartable {

    static func max<T: Comparable>(dataSet: [T]) -> T? {
        return dataSet.max()
    }

    static func min<T: Comparable>(dataSet: [T]) -> T? {
        return dataSet.min()
    }
}

extension Int: SimplyChartable { }
extension Double: SimplyChartable { }

Double.max([1.2, 1.1, 1.3])  // 1.3
Int.min([12, 11, 13])  // 11

Just my two cents worth...

like image 86
koropok Avatar answered Sep 29 '22 10:09

koropok


This isn't exactly what you've asked for, since it doesn't let you call a static function directly from a protocol metatype. But since that, AFAIK, isn't possible in Swift currently, perhaps this would be the next best thing?

extension Sequence where Element == SimplyChartable {
    func max() -> SimplyChartable {
        // put your implementation here
    }
}

You can then call this by just:

let arr: [SimplyChartable] = ...
let theMax = arr.max()
like image 39
Charles Srstka Avatar answered Sep 29 '22 09:09

Charles Srstka


For your situation, it's much better to use an Array extension rather than a protocol with an array parameter.

To handle each possible type of array i.e [Int], [Double] or [Float], create a wrapper enum with associated types as follows:

public enum SimplyChartableType {
  case int(Int)
  case float(Float)
  case double(Double)

  func getValue() -> NSNumber {
    switch self {
    case .int(let int):
      return NSNumber(value: int)
    case .float(let float):
      return NSNumber(value: float)
    case .double(let double):
      return NSNumber(value: double)
    }
  }

  init(int: Int) {
    self = SimplyChartableType.int(int)
  }

  init(float: Float) {
    self = SimplyChartableType.float(float)
  }

  init(double: Double) {
    self = SimplyChartableType.double(double)
  }
}

You can extend Array as follows:

extension Array where Element == SimplyChartableType {
  func max() -> SimplyChartableType {
    switch self[0] {
    case .int(_):
      let arr = self.map({ $0.getValue().intValue })
      return SimplyChartableType(int: arr.max()!)
    case .double(_):
      let arr = self.map({ $0.getValue().doubleValue })
      return SimplyChartableType(double: arr.max()!)
    case .float(_):
      let arr = self.map({ $0.getValue().floatValue })
      return SimplyChartableType(float: arr.max()!)
    }
  }
}

Example usage is:

var array = [SimplyChartableType.double(3),SimplyChartableType.double(2),SimplyChartableType.double(4)]
var max = array.max()

And now it's a lot easier to operate on Int, Double or Float together with:

extension SimplyChartableType: SimplyChartable {
  //insert functions here
  static func randomFunction() -> SimplyChartableType {
    //perform logic here
  }
}

The above snippet is good if you need a different functionality which operates on non-Collection types.

like image 28
Pranav Kasetti Avatar answered Sep 29 '22 11:09

Pranav Kasetti


This doesn't answer your specific question, unfortunately. Perhaps a work around to use a free function and casting.

import UIKit

protocol SimplyChartable {

    func chartableValue() -> Double

}

extension Int: SimplyChartable {

    func chartableValue() -> Double {
        return Double(self) ?? 0
    }

}

extension Double: SimplyChartable {

    func chartableValue() -> Double {
        return self
    }
}

extension Float: SimplyChartable {

    func chartableValue() -> Double {
        return Double(self) ?? 0
    }

}

func maxOfSimplyChartables(_ dataSet: [SimplyChartable]) -> SimplyChartable {
    return dataSet.max(by: { (lhs, rhs) -> Bool in
        return lhs.chartableValue() < rhs.chartableValue()
    }) ?? 0
}

let chartableItem1: SimplyChartable = 1255555.4
let chartableItem2: SimplyChartable = 24422
let chartableItem3: SimplyChartable = 35555

let simplyChartableValues = [chartableItem1, chartableItem2, chartableItem3]

maxOfSimplyChartables(simplyChartableValues)
like image 30
Vader Avatar answered Sep 29 '22 09:09

Vader