[swift-evolution] Support for newtype feature/typesafe calculations

Jeremy Pereira jeremy.j.pereira at googlemail.com
Tue Jan 5 11:19:24 CST 2016


I’m not saying your proposal would not be useful, but in the case of all of your examples, there are solutions that can be implemented now that might be better than just creating a new-type-but-exactly-the-same.

> On 5 Jan 2016, at 16:11, Grzegorz Adam Hankiewicz via swift-evolution <swift-evolution at swift.org> wrote:
> 
> The other day I was reading about https://www.reddit.com/r/swift/comments/3zbk64/type_math/

The idea of having a different type for each unit of distance is just wrong. You have one type for Distance with a value in a canonical unit and properties that extract the value in different units e.g

struct Distance
{
    let m: Double // SI unit of distance
    var km: Double { return m / 1000 }
    var cm: Double { return m * 100 }
    var miles: Double { return km * 0.62 }
}

Then you only need one set of addition and subtraction operators. You would also have multiplication by a dimensionless constant yielding a Distance and multiplication of two Distances yielding an Area.


> struct SomeTable {
>    var primaryKey: Int64
>    var otherTableForeignKey: Int64
>    var yeatAnotherForeginKey: Int64
> }
> 
> Unfortunately the types can be mixed, one can assign the value from otherTableForeignKey to primaryKey without impunity.
> Using the reddit proposed struct as separate type would help here:
> 
>    public struct RefTablePk {
>        private let value: Int64
>    }
> 
>    public struct SomeTablePk {
>        private let value: Int64
>    }
> 
>    struct RefTable {
>        var primaryKey: RefTablePk
>    }
> 
>    struct SomeTable {
>        var primaryKey = SomeTablePk(value: 0)
>        var otherTableForeignKey = RefTablePk(value: 0)
>    }
> 
>    var a = SomeTable()
>    a.primaryKey = SomeTablePk(value: 3)
>    a.otherTableForeignKey = a.primaryKey // Fails, good!
>    print(a.primaryKey.value)

That all looks fine except, why not take advantage of nested types:

struct SomeTable
{
    struct PrimaryKey { let value: Int }

    var primaryKey: PrimaryKey
    var refTableId: RefTable.PrimaryKey
}

> 
> So far so good. The solution gets more hairy when one attempts to use such fake types for mathematical operations

Why would you want to do mathematical operations on the primary key of a table? Primary keys obviously need to be Equatable and possibly Comparable (except what if the database uses a GUID as its key) but that is about it.

>    public struct Euros {
>        private let value: Double
>    }
> 
>    public struct Dollars {
>        private let value: Double
>    }
> 
>    var account = Euros(value: 100)
>    var bill = Euros(value: 42)
>    account = account - bill
> 
> The last line won’t compile: Binary operator ‘-‘ cannot be applied to two ‘Euros’ operands.

How about a Currency protocol?

protocol Currency
{
    // value in smallest unit of currency e.g. EUR = cents, GBP = pence
    var rawValue: Int64 { get }
    init(rawValue: Int64)
}

func -<T: Currency>(a: T, b: T) -> T
{
    return T(rawValue: a.rawValue - b.rawValue)
}

struct EUR: Currency
{
    var rawValue: Int64
}

struct USD: Currency
{
    var rawValue: Int64
}

let foo = EUR(rawValue: 2000)
let bar = EUR(rawValue: 1000)

let baz = foo - bar // compiles

let whizz = USD(rawValue: 4000)

let biz = baz - whizz // error: binary operator '-' cannot be applied to operands of type 'EUR' and 'USD'

Only one - operator needs to be defined for all currencies and the fact that EUR and USD are intended to model currencies is self documenting.

So I don’t think any of your examples are compelling evidence for your proposal. That’s not to say there are not good reasons for it, I just haven’t seen them yet.



More information about the swift-evolution mailing list