[swift-evolution] Support for newtype feature/typesafe calculations
James Campbell
james at supmenow.com
Thu Jan 7 06:06:01 CST 2016
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
-------------- next part --------------
An HTML attachment was scrubbed...
URL: <https://lists.swift.org/pipermail/swift-evolution/attachments/20160107/d498b5f3/attachment.html>
More information about the swift-evolution
mailing list