Yep, that is why I like the style that you can do with current code.  :-)

On Friday, July 29, 2016 at 6:35:46 AM UTC-6, John Bugner wrote:
>
> Also, thinking about this further, any definition of conversions between 
> types is probably best expressed as a function, because a conversion is not 
> always a simple multiple (like 1 yard = 3 feet). Take, for example, the 
> conversion between degrees celsius and degrees fahrenheit. (Note that if 
> `Temp` was a unit type, it's "base" unit should be Kelvin (or Rankine, I 
> suppose.), because Kelvin starts at 0, which lets multiplying temperatures 
> by a raw number make sense.)
>
> On Friday, July 29, 2016 at 7:11:19 AM UTC-5, John Bugner wrote:
>>
>> >This would be a nice fix to the Radians / Degrees issue too. Every 
>> language seems to settle on one or the other as "the base unit" but 
>> newcomers have to learn the assumption or face bad accidental outputs.
>> Yes, `degrees`, `radians`, and `turns` in `Basics` suffer the same 
>> problem.
>>
>> >F# is kind of radical in that it has units.
>> I didn't know this! (I have heard of F#, but I had never used or read 
>> some of its code.) It pleases me to know that I'm not the only one to think 
>> that is a problem, and that it can be solved by this solution! (F#'s 
>> implementation seems slightly different from what I am imagining, but close 
>> enough.)
>>
>> I'm curious though, does F# use its unit/measure types widely? or do 
>> common library functions (like those about time, angles, etc) still take a 
>> raw float type where it could take a unit/measure float type? How often do 
>> common 3rd-party libraries use this feature? How often do average-joe 
>> programmers use this feature?
>>
>> >Could possible do it 'now' with
>> >...
>> >If you do not expose the constructor of the type then it should be a 
>> fully opaque type, only able to be constructed via things like 
>> `milliseconds 500` or so.
>> As a work-around that uses only current features, I like this. It's an 
>> improvement over the current system of just having `Time` be an alias of 
>> `Float`. (Heck, `Angle` isn't even an alias! The angle functions' 
>> signatures are just `Float -> Float`!)
>>
>> On Thursday, July 28, 2016 at 3:13:27 PM UTC-5, OvermindDL1 wrote:
>>>
>>> Could possible do it 'now' with
>>>
>>> ```elm
>>> type Time
>>>   = Time_Value Float
>>>
>>> milliseconds m = seconds <| m/1000
>>>
>>> seconds s = Time_Value s
>>>
>>> minutes m = seconds <| m*60
>>>
>>> from_json json =
>>>   Json.Decode.decodeString Json.Decode.float json |> Result.map (\t -> 
>>> Time_Value t)
>>>
>>> to_seconds (Time_Value t) = t
>>>
>>> to_minutes t = 60 * (to_seconds t)
>>>
>>> to_json t = Json.Encode.float <| to_seconds t
>>> ```
>>>
>>> If you do not expose the constructor of the type then it should be a 
>>> fully opaque type, only able to be constructed via things like 
>>> `milliseconds 500` or so.
>>>
>>>
>>> On Thursday, July 28, 2016 at 12:42:44 PM UTC-6, John Bugner wrote:
>>>>
>>>> In the time library ( 
>>>> package.elm-lang.org/packages/elm-lang/core/4.0.3/Time ), there are 
>>>> many functions under the "Units" heading. Under the function 
>>>> `millisecond`, 
>>>> it says:
>>>> >Units of time, making it easier to specify things like a half-second 
>>>> (500 * millisecond) without remembering Elm’s underlying units of time.
>>>> I find this underwhelming, because nothing forces me to use these unit 
>>>> functions. I could write `every 5 toMsg` and the compiler will not stop 
>>>> me. 
>>>> How long of a time am I actually specifying? Probably 5 milliseconds, 
>>>> because I infer that it's probably using the same unit as JavaScript, but 
>>>> I 
>>>> can't know for sure. A beginner might think 5 seconds, because it's the 
>>>> unit that has no prefixes, or 5 minutes if 5 minutes seems appropriate to 
>>>> what the application does. This is very bad; There should never be doubt 
>>>> about what unit is being used, and the compiler should enforce this unit 
>>>> correctness. Currently, Elm can't do this, because `Time` is just an alias 
>>>> for `Float`.
>>>>
>>>> To prevent this kind of error, I propose a new language construct that 
>>>> I call a "unit type". It would have the following properties:
>>>> (1) A definition that would look (very roughly) like this:
>>>> ```
>>>> type unit Time as Float
>>>>     = Second
>>>>     | Millisecond == Second 0.0001
>>>>     | Nanosecond == Millisecond 0.0001
>>>>     | Minute == Second 60
>>>>     | Hour == Minute 60
>>>>     | Day == Hour 24
>>>>     | Year == Day 365
>>>> ```
>>>>
>>>> The point of the definition is to:
>>>> (a) Provide an easy way to define different units that measure the same 
>>>> thing (in this case, time, but you could do the same thing for 
>>>> length/height/depth in either metric or US imperial units) as constructors.
>>>> (b) Tell how they are related to eachother. (A minute is 60 seconds, an 
>>>> hour is 60 minutes, etc.) The compiler would check that all relations of 
>>>> the constructors eventually flow to a single base unit (in this case, 
>>>> `Second`). A cycle would be disallowed.
>>>> (c) Tell what type the unit is based on (in this case, `Float`). 
>>>> (Perhaps non-number types would be disallowed.)
>>>>
>>>> (2) Given that `every` still has the same signature: `Time -> (Time -> 
>>>> msg) -> Sub msg`, `every 5 toMsg` would cause a compile-time error, 
>>>> because 
>>>> the types don't match; The function expects a number with a `Time` unit, 
>>>> but receives `5`, which is just a raw number (a number without any unit).
>>>> (3) When writing a function that has a parameter of type `Time`, 
>>>> pattern matching only matches the base unit constructor, not every 
>>>> constructor like a normal (data/enum) type. The compiler would 
>>>> automatically convert the other units to the base unit with the 
>>>> conversions 
>>>> that the programmer provided in the definition.
>>>> (4) Comparison, addition, and subtraction would be automatically 
>>>> implemented for the type, so two times of any combination of constructors 
>>>> could be compared, added, or subtracted with ease. `(Minute 5) - (Second 
>>>> 20) == (Millisecond 280000)` would "just work". "Time + Float" (or "Time + 
>>>> Length") would cause a compile-time error.
>>>> (5) Perhaps compound unit types like "Time^2" would be supported, so a 
>>>> "Time * Time" would yield "Time^2", "Time * Float" would yield "Time", and 
>>>> "Time * Length" would yield just that: "Time * Length". ("Force" would be 
>>>> an alias of "Mass * Length / (Second^2)".)
>>>>
>>>> I know that this is a very radical proposal, (I don't know of any 
>>>> language that has a feature like this.) but I bring it up anyways, because 
>>>> although it's been 18 years since the mars probe crashed because of a unit 
>>>> error (one module assumed that the number it was getting was in metric 
>>>> units, and another assumed that it was getting it in US imperial units) ( 
>>>> https://en.wikipedia.org/wiki/Mars_Climate_Orbiter ), I'm amazed that 
>>>> since then, programming languages have done nothing to prevent this kind 
>>>> of 
>>>> error from happening again, besides just admonishing programmers to be 
>>>> careful. (As if the NASA programmers at the time weren't already trying 
>>>> to!)
>>>>
>>>> Letting `5` be a legal unitless `Time` value is just as silly and 
>>>> dangerous as letting `bool a = 2;` be a legal statement in C. data/enum 
>>>> types prevent this from happening to `Bool` in Elm, and unit types could 
>>>> prevent the same kind of thing from happening to `Time`.
>>>>
>>>> Questions, comments, related thoughts, etc are welcome.
>>>>
>>>>

-- 
You received this message because you are subscribed to the Google Groups "Elm 
Discuss" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
For more options, visit https://groups.google.com/d/optout.

Reply via email to