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

James Campbell james at supmenow.com
Thu Jan 7 06:07:47 CST 2016


This is my current implementation of this pattern.

On Thu, Jan 7, 2016 at 12:06 PM, James Campbell <james at supmenow.com> wrote:

> So this is how this feature could be achieved right now with minimal
> changes to the language.
>
> - We expose some kind of protocol that allows you to Box up types, we
> could call this `Box` or something else like `Unit`. (I have a working
> implementation in the current language with a bit of boilerplate). This
> protocol handles the default implementation of converting from literals and
> to floats etc.
> - For each type-safe unit for a calculation - you define a protocol
> extending this `Box` type which defines the associated type of the value
> that unit holds. For example for Degree and Radian I declared a `AngleType`
> which set the associated type to be a double.
> - For each unit type, you declare a struct that inherits from that
> protocol you defined. So I have two structs `Degree` and `Radian` which
> implement the `AngleType` protocol.
> - You implement the functions for figuring out if your units are equal and
> all other operators they may need i.e `Degree(360) - 30`.
>
> Future improvements with language updates:
>
> - `AngleType` protocol may not be needed if swift introduces generic
> protocols.
> - Boilerplate in your type safe unit types may be reduced if swift
> introduces memberwise initialization.
> - The current implementation of this system in Swift will be greatly
> simplified once the refactoring of Swift's Number types has been completed.
> We currently use a bunch of work arounds with the compiler.
> - We could introduce custom user literals to create these types if swift
> supports this in the future (As Felix states).
>
> On Thu, Jan 7, 2016 at 7:06 AM, Félix Cloutier <felixcca at yahoo.ca> wrote:
>
>> I'd like to hijack this thread to talk about what the TI Voyage 200 could
>> do as far as "type-safe calculations" go.
>>
>> The calculator supports symbolic equations (for instance, `a * 1000 / 2`
>> results in `500a` when a isn't known). The interesting part is that you
>> could append units to numbers. For instance, you can write `500_m`, and
>> this means 500 meters. Numbers with a type can only be added/subtracted
>> with numbers of the same type, but they can be multiplied or divided by
>> pretty much anything. For instance, `500_m - 2_s` (500 meters minus 2
>> seconds) is an error, but `500_m / 2_s` is `250 (_m/_s)` (or _m * _s^-1).
>>
>> I found this *extremely* useful for engineering calculations. For
>> instance, when you multiply quantities that should end up in Teslas, you
>> know that you've done something wrong if the unit displayed after the
>> number doesn't look like `V * s * m^-2`. It was also a lifesaver that you
>> could do something like `6_ft` and end up with 1.8288_m (because _ft is
>> defined as 0.3048_m).
>>
>> I have no idea how you'd implement that with Swift though. I'm not a very
>> powerful template wizard, but I have no idea how you'd do it with C++
>> either.
>>
>> Of course, it might be a few years before you're allowed to use the Swift
>> compiler during your physics exams, and I don't think that real-world
>> programs often need that much unit safety with numbers. But when I read
>> "epic typesafe calculations", that's what I think about.
>>
>> Félix
>>
>> Le 7 janv. 2016 à 01:42:07, Thorsten Seitz via swift-evolution <
>> swift-evolution at swift.org> a écrit :
>>
>> I think the name should be changed to NumberBox or something similar. A
>> box is something very generic and should not have number related associated
>> types.
>>
>> -Thorsten
>>
>> Am 06. Januar 2016 um 18:15 schrieb James Campbell <james at supmenow.com>:
>>
>> I've managed to implement this already in the language with a few ugly
>> corners due to the lack of generic protocols.
>>
>> I created a protocol based on Box (https://github.com/robrix/Box/) which
>> works really well. I have extended this to handle certain special protocols
>> like Equatable so you can do SpecialType == SpecialType, and even
>> literalConversion.
>>
>> There is however a lot of boilerplate:
>>
>> - You have to declare all of your Convertible protocols for converting
>> from one type to another
>> - You have to define an empty init so the protocol extensions have
>> something to chain to.
>> - You need to write the value property with type.
>>
>> Due to the lack of protocol generics, you also need to have a protocol
>> for every type you wish to box which sets the associated type. Of course I
>> could have done this with classes but I wanted to keep this as a value type
>> :).
>>
>> With member-wise initializations and generic protocols this could be
>> achievable just by adding a Box protocol to the standard library.
>>
>> Here is my implementation of Box as a protocol:
>>
>> *protocol Box: CustomStringConvertible, CustomDebugStringConvertible {*
>>
>>
>>
>> *    typealias FloatLiteralType = Double*
>>
>> *    typealias IntegerLiteralType = Int*
>>
>> *    typealias BoxType = Any*
>>
>>
>>
>> *    var value: BoxType { get set }*
>>
>>
>>
>> *    init()*
>>
>> *    init(_ value: BoxType)*
>>
>> *}*
>>
>>
>> *extension Box where BoxType: CustomStringConvertible {*
>>
>>
>>
>> *    var description: String {*
>>
>> *        return self.value.description*
>>
>> *    }*
>>
>>
>>
>> *    var debugDescription: String {*
>>
>> *        return "\(self.value.description)㎭"*
>>
>> *    }*
>>
>> *}*
>>
>>
>> *//MARK: FloatingPointBox*
>>
>>
>> *protocol FloatingPointBox: Box, FloatLiteralConvertible,
>> IntegerLiteralConvertible {*
>>
>>
>>
>> *    typealias BoxType = Double*
>>
>> *    typealias FloatLiteralConvertible = Double*
>>
>> *    typealias IntegerLiteralConvertible = Int*
>>
>> *}*
>>
>>
>> *extension Box where Self.BoxType == Double {*
>>
>>
>>
>> *    init(_ value: Double) {*
>>
>>
>>
>> *        self.init()*
>>
>> *        self.value = value*
>>
>> *    }*
>>
>>
>>
>> *    init(_ value: Int) {*
>>
>>
>>
>> *        self.init()*
>>
>> *        self.value = Double(value)*
>>
>> *    }*
>>
>> *}*
>>
>>
>> *extension FloatLiteralType {*
>>
>>
>>
>> *    init<T: Box where T.BoxType == Double >(_ box: T) {*
>>
>> *        self.init(box.value)*
>>
>> *    }*
>>
>>
>>
>> *    init<T: Box where T.BoxType == Int >(_ box: T) {*
>>
>> *        self.init(box.value)*
>>
>> *    }*
>>
>> *}*
>>
>>
>> *extension CGFloat {*
>>
>>
>>
>> *    init<T: Box where T.BoxType == Double >(_ box: T) {*
>>
>> *        self.init(box.value)*
>>
>> *    }*
>>
>>
>>
>> *    init<T: Box where T.BoxType == Int >(_ box: T) {*
>>
>> *        self.init(box.value)*
>>
>> *    }*
>>
>> *}*
>>
>>
>> *//Adding FloatLiteralConvertible, IntegerLiteralConvertible*
>>
>>
>> *extension FloatingPointBox where Self.BoxType == Double,
>> Self.FloatLiteralConvertible == Double {*
>>
>>
>>
>> *    init(floatLiteral value: Double) {*
>>
>> *        self.init(value)*
>>
>> *    }*
>>
>>
>>
>> *    init(integerLiteral value: Int) {*
>>
>> *        self.init(value)*
>>
>> *    }*
>>
>>
>>
>> *    init<T: IntegerType>(_ value: T) {*
>>
>> *        self.init(value)*
>>
>> *    }*
>>
>> *}*
>>
>> Here is my example of using the Box protocol:
>>
>> *struct Degree: FloatingPointBox {*
>>
>>
>>
>> *    var value: Double = 0*
>>
>>
>>
>> *    init()*
>>
>> *    {*
>>
>> *    }*
>>
>> *}*
>>
>>
>> *protocol DegreeConvertiable {*
>>
>>
>>
>> *    init(degreeLiteral value: Degree)*
>>
>> *}*
>>
>>
>> *extension Degree: RadianConvertiable {*
>>
>>
>>
>> *    init(radianLiteral value: Radian) {*
>>
>> *        self.value = Double(value) * 180.0 / M_PI*
>>
>> *    }*
>>
>>
>>
>> *    init(_ value: Radian) {*
>>
>> *        self.init(radianLiteral: value)*
>>
>> *    }*
>>
>> *}*
>>
>> On Tue, Jan 5, 2016 at 5:24 PM, Matthew Johnson via swift-evolution <
>> swift-evolution at swift.org> wrote:
>>
>>>
>>> > On Jan 5, 2016, at 11:16 AM, Thorsten Seitz via swift-evolution <
>>> swift-evolution at swift.org> wrote:
>>> >
>>> >
>>> >> Am 05.01.2016 um 17:11 schrieb Grzegorz Adam Hankiewicz via
>>> swift-evolution <swift-evolution at swift.org>:
>>> >>
>>> >> The ideal would be for the compiler to pretend Euros or RefTablePk
>>> are different types, yet use their parent type at the binary level. This
>>> needs a specific syntax to teach the compiler which existing
>>> methods/operations are allowed on the new fake types and which aren’t.
>>> These new distinct types would *borrow* previous implementations.
>>> >
>>> > What about citing the relevant protocols in the newtype definition?
>>> This should include the ability to use my own protocols to which I have
>>> made the underlying type conform to by an extension.
>>>
>>> This is how my forwarding proposal works.  The newtype syntax I
>>> suggested as a possible extension looks like this:
>>>
>>> newtype Euro = Double forwarding Addable, Subtractable
>>>
>>> The keyword could be different, but I think `forwarding` is not bad.
>>> When I complete the second draft I think it will make even more sense.  The
>>> forwarding facility has features to handle non-trivial cases (Self and
>>> associated type requirements, etc).
>>>
>>> >
>>> > Throwing some syntax into the discussion:
>>> >
>>> > newtype Euro = Double : Addable, Subtractable
>>> >
>>> > where I have defined the protocols Addable and Subtractable somewhere
>>> and made Double conform to them if all this is not provided by the standard
>>> library.
>>> > The implementation of Euro then borrows the implementation of Double
>>> for these protocols.
>>> >
>>> > -Thorsten
>>> > _______________________________________________
>>> > swift-evolution mailing list
>>> > swift-evolution at swift.org
>>> > https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>> _______________________________________________
>>> swift-evolution mailing list
>>> swift-evolution at swift.org
>>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>>
>>
>>
>>
>> --
>>  Wizard
>> james at supmenow.com
>> +44 7523 279 698
>>
>> _______________________________________________
>> swift-evolution mailing list
>> swift-evolution at swift.org
>> https://lists.swift.org/mailman/listinfo/swift-evolution
>>
>>
>>
>
>
> --
>  Wizard
> james at supmenow.com
> +44 7523 279 698
>



-- 
 Wizard
james at supmenow.com
+44 7523 279 698
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160107/7e1facae/attachment.html>
-------------- next part --------------
A non-text attachment was scrubbed...
Name: TypeSafeUnits.zip
Type: application/zip
Size: 34042 bytes
Desc: not available
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160107/7e1facae/attachment.zip>


More information about the swift-evolution mailing list