Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Will this design work out? #2

Closed
jkroso opened this issue Sep 7, 2017 · 2 comments
Closed

Will this design work out? #2

jkroso opened this issue Sep 7, 2017 · 2 comments

Comments

@jkroso
Copy link
Owner

jkroso commented Sep 7, 2017

@ajkeller34 I'd like to see what you think of this approach to units. You know a lot more about scientific units and Julia than I do which makes me worried I'll waste your time but still I do feel like I'm on to something.

I was motivated to experiment with this because I really struggle to understand how all of Unitful's types fit together and honestly I still don't really. Maybe it's just me though.

And I'm encouraged by it handling some things better than Unitful. I'm not sure if it's anything inherent in Unitful's design that stops it handling these cases but I'll list them anyway for you to decide.

  • with Unitful 1kg/m * 1cm == 1 kg cm m^-1, with this package 1kg/m * 1cm == 0.01kg. I'm not sure if Unitful's answer is equivalent or not because my algebra is a bit 🥔 but it definitely doesn't look as nice.
  • with Unitful 1mm + 1cm == 11//1000 m, with this package 1mm + 1cm == 11mm. This case is definitely just vanity though I know.

About the design

  • All dimensions (Length, Mass, etc..) are defined as subtypes of Dimension
  • Simple derived types like Area are defined using the Exponent{n,Dimension}
  • All other derived types are defined using Combination{Tuple{Exponent}

Whenever you do any math with units it will convert all units to Combination's. Perform the math then convert the result back down to the simplest type possible. So (1m*1m)::Length^2 and (1m²/1m)::Length. This means that you only have to define one math method to support all kinds of units; like Unitful. While only ever exposing one definition of a certain type to the user. And that type might be one they themselves defined.

Currently it will be really slow but I think generated functions can solve that just as they did with Unitful

@ajkeller34
Copy link

There are some good ideas here, my favorite being the ability to do mathematics using the dimension types (e.g. ::Length^2 or ::Length/Time). This could be added to Unitful without changes to the way the types are structured, with your approval of course.

There's nothing inherent in Unitful that prevents the simplifications you prefer. A robust simplification mechanism is planned at some point or another. There's a PR I have been meaning to address for a while now that I should really do soon and your feedback is helpful to let me know that simplification should be a priority.

I'm not sure I understand your type hierarchy. You have e.g. Length <: Dimension <: Unit <: Number. I don't see quantities anywhere, a quantity being a number with an attached unit. Maybe it's just language and you use the word "unit" to mean quantity? Also, units have dimensions, so Dimension <: Unit is a little confusing to me.

A few comments on what you say in your README file:

  1. you write "with Unitful when you define a new dimension type your quantities are not actually instances of that type. So it's hard to extend the behavior of those objects." I should caution that this will not be true for you either if you want performant code. First of all, you'll need at least one type parameter to specialize on the underlying numeric type. If you just have value::Real, you'll never have good performance since Real is abstract. If you also want unit conversions to be compiled and have minimal run-time penalties, you'll also need to store the units in the type signature somehow, otherwise there will be some run-time lookups and such. So, while you can say (1m*1m)::Length^2 the way your code is currently written, actually you'll eventually have (1m*1m)::Length{Int,units...}^2 or something like that. In which case, Length^2 would be an abstract type because it has no type parameters specified, and so your quantities again would not be instances of that dimension type, just like with Unitful. You'll wind up with types that look very similar, I'm pretty sure.

  2. You write, "So it's hard to extend the behavior of those objects. It's doable I'm sure but not obvious." What behavior do you find hard to extend in Unitful? You can implement methods on ::Quantity, or ::Quantity{Float64}, or ::Length, or ::Length{Int}, or even ::typeof(1.0m). I guess the only thing you can't do easily at the moment is ::Meter. That's not fundamental though—I could implement that without any other changes, but usually one doesn't want to dispatch on units alone. The point is usually to write generic code that can handle different units transparently. Please let me know if I'm missing something, or don't hesitate to file issues at Unitful.

  3. Finally, you know about the @unit and @derived_dimension macros in Unitful? It's just a one-liner to define new units based on existing ones. You don't even have to subtype yourself, you just get the unit object or the dimension alias automatically after the macro is done.

Fundamentally, the approaches for all type-based unit packages end up converging even if there are some details in implementation. The unit information needs to be encoded in the type signature, possibly with dimensional information if it isn't clear from the allowed units, and then you need to specialize on the numeric type. There's no way around that, I think. Since there's already a lot of adoption for both Unitful and SIUnits, I think it makes more sense to open some PRs in either place rather than reinvent the wheel. When I wrote Unitful I was trying to solve specific problems that could not be addressed without some serious changes to SIUnits: arbitrary unit support, better handling for overflows (e.g. Keno/SIUnits.jl#22), fractional exponents (e.g. Keno/SIUnits.jl#32), and so on.

I guess my advice would be to clarify what fundamental problem you are solving by writing a new units package, and why it cannot be addressed by smaller changes to existing packages. But don't let me stop you from experimenting, you've already found a good idea with the ::Length/Time thing! I hope this feedback ends up being helpful.

@jkroso
Copy link
Owner Author

jkroso commented Sep 7, 2017

This could be added to Unitful without changes to the way the types are structured, with your approval of course.

For sure. I'm not really wanting to write my own package so much as just experiment. I'd like to see Unit support become part of Base eventually anyway because Units are so fundamental. I think the only reason no language has them built in is because they are hard to do well.

I don't see quantities anywhere, a quantity being a number with an attached unit

I think of a unit as being a type and a quantity as being an instance of a unit. So in my implementation I would call 1m a quantity and m a unit.

I'm not sure I understand your type hierarchy. You have e.g. Length <: Dimension <: Unit <: Number

I use "Dimension" to mean primitive unit. The wikipedia article on SI Units says "The SI base units form a set of mutually independent dimensions". Which to me indicates "dimension" means "base unit". But the wikipedia article on dimensions seems to use the word a bit differently and I think more in line with the way you use it.

I think I will rename Dimension to BaseUnit. The reason that type exists is just to differentiate base units from derived units. Knowing that does the hierarchy seem sensible?

you'll need at least one type parameter to specialize on the underlying numeric type

Ah yes this is critical. I avoided this because it causes a proliferation of UnionAll types and Julia doesn't seem to make it very easy to deal with them very well. I'm getting the hang of it though and I think it might be possible to do it well. I had overlooked how hard it might be though.

What behavior do you find hard to extend in Unitful?

Honestly I just can't wrap my head around Unitful's implementation. So it's really hard for me to provide good feedback. How would you implement my Money type with Unitful?

@jkroso jkroso closed this as completed Oct 13, 2020
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

2 participants