I work in the cabinet industry. In our code we make heavy use of size-centric API--I'm not sure if this would be generally useful but I figured I'd show you how it works. In Fan these would look like:
What I like in particular about this approach is how the method names make the code very intentional: Dist.mm is a measurement of distance expressed in millimeters; Dist.inch is a measurement of distance expressed in inches.
I've been thinking about how this could be modeled more generically into a Measure mixin:
mixin Unit
{
Decimal ratio(This other)
}
mixin Measure
{
Decimal val()
Unit unit()
This convert(Unit unit)
// Can't believe I remember these terms from elementary school..
This plus(This addend)
This minus(This subtrahend)
This mult(Decimal multiplicand)
This div(Decimal divisor)
// etc for other math functions..
}
Thus:
enum DistUnit
{
mi, yd, ft, inch, km, m, cm, mm, nm
// ratio(DistUnit) omitted for brevity
}
class Dist : Measure
{
new mi(Decimal val)
new yd(Decimal val)
new ft(Decimal val)
new inch(Decimal val)
new km(Decimal val)
new m(Decimal val)
new cm(Decimal val)
new mm(Decimal val)
new nm(Decimal val)
new make(Decimal val, DistUnit unit)
DistUnit unit() // covary return type by concrete class
}
This same concept could be applied to other measurements, e.g:
enum MassUnit
{
kg, g, mg
}
class Mass : Measure
{
new kg(Decimal val)
new g(Decimal val)
new mg(Decimal val)
new make(Decimal val, MassUnit unit)
MassUnit unit() // covary return type by concrete class
}
Maybe some more specialized APIs like this would be more humane than the current sys::Unit class. :)
brianWed 20 May 2009
There is a difference between modeling units and quantities that have a unit. The unit API is a database of units so that higher layers can agree what things like "meter" actually mean.
When it comes to quantities, what happens if I want Ints or Floats? Or maybe a list of Int, Float, or Decimal? Would I create create a wrapper class for every dimension with methods for every unit? I participated in the units JSR which tried to model units with the type system, and I personally didn't really like the result.
So my only goal is to define a normalized representation for what a Unit is so that higher levels can share. How you attach that as meta-data to the values is an application layer issue.
jodastephenThu 21 May 2009
This is a topic that has come up a lot, so we shouldn't see it as solved yet.
Personally, I'd like to see a new type of literal for units and quantities. I haven't worked out the details, but I think:
// user codes
3_miles
6_metres
10.5_years
// I'm thinking these are mapped to calls to a constructor
Distance.miles(3)
Length.metres(6)
Duration.years(10.5)
// plus we'd probably need a using clause
using literal Distance
As I say, its and outline concept. The tricky part is the using statement (or similar) that causes the compiler to allow specific literal suffixes.
The nice part is that it removes the need for dedicated Duration literals. So, its a unification proposal.
qualidafialThu 21 May 2009
When it comes to quantities, what happens if I want Ints or Floats?
We could change all the sys::Decimal fields and method args to sys::Num if that makes a difference.
Would I create create a wrapper class for every dimension with methods for every unit?
I assume you're talking about object overhead? In practice this has never been a problem, and the ability to combine measurements instead of numbers has simplified things like adding measurements of different units:
a := Dist.inch(1)
b := Dist.mm(25.4)
c := a+b
echo(c) => 2"
My only complaint so far has been Java doesn't allow operator overloading, so things like a*b+c have to be coded as a.mult(b).plus(c) which is noisy (and a little annoying).
I participated in the units JSR which tried to model units with the type system, and I personally didn't really like the result.
Which JSR?
// plus we'd probably need a using clause using literal Distance
You probably also want a literal keyword or annotation on just the constructors that make sense as literals, so that ctors like make doesn't get dragged into the mix:
new literal cm(Num val) // as keyword
@literal new mm(Num val) // as annotation
new make(Num val, DistUnit unit) // not a literal
On another note, I'm wondering if the compiler is going to treat these two expressions the same:
Dist a := Dist.inch(1)
Decimal b := 2
Dist c := a*b // this compiles, but
Dist d := b*a // does this?
For commutative math functions like * it may make sense for the compiler to try the operands in the opposite order if the second operand doesn't fit the method argument type for the first operand.
brianThu 21 May 2009
I assume you're talking about object overhead?
I was more talking about the 60 different quantities the unit database has already. But object overhead has an impact too (especially if you box to Num). In my field, units are almost always attached as meta-data and you never code with a specific unit directly (time aside). Plus I typically work with millions, if not billions of points of data, and overhead is pretty important to me. This is why it is common with tabular data to attach the unit meta-data to the column, not each individual value.
I think the main point here, is that how units are used is very much an application layer issue. I am not sure there is a good generic solution, in the mean-time my only goal is to created a normalized representation for the definition of a unit (which is actually immensely useful regardless of how the higher layers are implemented).
For commutative math functions like * it may make sense for the compiler to try the operands in the opposite order if the second operand doesn't fit the method argument type for the first operand
The compiler does not do this today. It seems reasonable for + or *, although it definitely would muddy the elegance of operators mapping directly to methods.
qualidafialThu 21 May 2009
The lack of slot name overloading also makes things like this impossible:
You cannot have both because slot name overloading is not allowed, unless there is some other way to make a method into a math function. Maybe facets can help us here:
class Dist {
Dist div (Num dividend)
@div Speed divDuration(Duration duration)
@div Num divDist (Dist dividend)
}
KevinKelleyThu 21 May 2009
This units thing sounds like a really good first test for the DSL proposal. Rather than trying to forcefit ring algebras and all that whatnot into the base language, write a DSL that understands all the proper associativities, with its own syntax; have a language feature for communicating back and forth to it.
Core Fan shouldn't be trying to provide syntax for everything from furlongs to string theory; but if that DSL thing works right it can cleanly do all that anyway.
brianThu 21 May 2009
The lack of slot name overloading also makes things like this impossible
It isn't impossible, you just have to type your method using Obj and give up static type checking.
tompalmerThu 21 May 2009
It isn't impossible, you just have to type your method using Obj and give up static type checking.
I still think it might be good to allow operator overloading without slot name overloading. For example, allow * to map to anything starting with mult (if the next letter isn't a lowercase letter?), and apply normal overloading rules from Java/C++ at that point. Or maybe a variation on that theme.
As I've mentioned before, this (operator overloading) is already supported in Fan for special cases: get and slice, and make and fromStr.
brianThu 21 May 2009
still think it might be good to allow operator overloading without slot name overloading. For example, allow * to map to anything starting with mult (if the next letter isn't a lowercase letter?),
I like that, although I would formalize the naming rule to be something like shortcut+typename:
I would formalize the naming rule to be something like shortcut+typename
I've thought the same, but I was wondering how pod names would play in. Just leave them out? The chance of needing to overload to two types with the same name from different pods seems slim. And if you really needed it, maybe you'd just need to work around by using standard calling conventions rather than operators? That might cover things well enough.
qualidafialThu 21 May 2009
> I would formalize the naming rule to be something like shortcut+typename I've thought the same, but I was wondering how pod names would play in.
I was wondering about pod names too. Using facets would remove ambiguity, I think:
class DateTime
{
@minus Duration minus(DateTime)
@minus DateTime minusDuration(Duration)
}
Using facets the compiler magic could be explicit instead of intrinsic in the naming convention:
class Dist
{
@div Dist div (Num divisor)
@div Num divDist (Dist divisor)
@div Speed divDuration(Duration duration)
}
class Speed
{
@mult Speed mult (Num factor)
@mult Dist multDuration(Duration duration)
}
speed := Dist.km(30) / 1hr => 30km/hr
distance := speed * 1.5hr => 45km
class DateTime
{
@minus DateTime minus(Duration duration)
@minus Duration minusDateTime(DateTime dateTime)
}
now := DateTime.now
tomorrow := now.plus(1day)
interval := tomorrow - now => 1day
> For commutative math functions like * it may make sense for the compiler to try the operands in the opposite order if the second operand doesn't fit the method argument type for the first operand
The compiler does not do this today. It seems reasonable for + or * although it definitely would muddy the elegance of operators mapping directly to methods.
I agree, only + and * would make sense for this. What if we used a facet so classes can opt in to this behavior case by case?
class Dist
{
@mult @commutative Dist mult(Num multiplicand)
}
class DateTime
{
@plus @commutative DateTime plus(Duration duration)
}
class Duration
{
@mult @commutative Duration mult(Num multiplicand)
}
qualidafialThu 21 May 2009
I'm going to go ahead and start a little project on bitbucket for this. I'll be naming the pod measure for lack of a better idea. (suggestions welcome)
I will need a few things from Fan to make these work elegantly:
Overloading operators either by naming convension (e.g. div vs divDuration) or by facets (e.g. @mult, @add).
Commutative addition and multiplication on + and * expressions when the first operand doesn't know how to add the second operand, but the second knows how to add the first. A facet seems the right approach to me but I'll take anything that works.
sys::Duration literals are converted to nanoseconds, and do not remember the unit of time used to define them. It would be useful to save that unit of time so it can be queried and referenced directly in code. e.g. a unit of speed is expressed as a unit of distance divided by a unit of time. Right now there is no enum in sys to represent a unit of time so if you guys do not want to support this natively in Fan I will probably have to duplicate Duration to achieve it.
A literal syntax as advocated by Stephen would be really great.
brianThu 21 May 2009
Overloading operators either by naming convension (e.g. div vs divDuration) or by facets (e.g. @mult, @add).
I am for this feature, although I prefer the explicit naming by type - this is a case where the convention should be the rule. What we've to hack around it in DateTime and Duration is ugly.
Commutative addition and multiplication on + and * expressions when the first operand doesn't know how to add the second operand,
I am not ready to support this feature, at the very least I need to think it about it a while. Although, since it won't ever work for - or /, I am not sure it really matters, the lhs will always have to be the type further down the dependency chain.
sys::Duration literals are converted to nanoseconds, and do not remember the unit of time used to define them. It would be useful to save that unit of time so it can be queried and referenced directly in code
It doesn't remember, in practice is hardly ever matters because things get rounded to the whole unit:
fansh> 1sec.toStr
1sec
fansh> 1hr.toStr
1hr
A literal syntax as advocated by Stephen would be really great.
I don't think this belongs on the radar for 1.0
jodastephenThu 21 May 2009
sys::Duration literals are converted to nanoseconds, and do not remember the unit of time used to define them.
And unfortunately this can be wrong when correctly evaluating dates and times. 1 minute is not necessarily the same as 60 seconds (leap seconds). Similarly for 1 day is not necessarily 24 hours (daylight savings).
As such, have a literal form that creates a Period (Joda-Time/JSR-310 terminology) that does know and remember what you created is a useful thing.
tompalmerThu 21 May 2009
1 minute is not necessarily the same as 60 seconds (leap seconds). Similarly for 1 day is not necessarily 24 hours (daylight savings).
Joda-Time isn't always as easy to work with as I'd like (sorry -- though I still like it much better than java.util.Calendar of course), but it teaches well the idea of using semantic terminology at the human level, when the human level is what matters (which I find to be often the case).
jodastephenThu 21 May 2009
I'd love to hear why and what difficulties you've faced with Joda-Time so I don't repeat the mistakes! But lets discuss it privately or on the Joda list, not here!
So far there's just Dist and DistUnit, I'll add the other units but this is a decent start.
tompalmerFri 22 May 2009
But lets discuss it privately or on the Joda list, not here!
Sorry. Bad form on my part. Problem will be that it's been a year since I last used it. If I get a chance to take a look again, I'll see if I can provide meaningful feedback in a more appropriate venue.
My main point here (lost in my poorly chosen comment) was definite agreement on the importance of being able to deal with time in terms of the units we use in human language.
qualidafial Wed 20 May 2009
I work in the cabinet industry. In our code we make heavy use of size-centric API--I'm not sure if this would be generally useful but I figured I'd show you how it works. In Fan these would look like:
What I like in particular about this approach is how the method names make the code very intentional:
Dist.mm
is a measurement of distance expressed in millimeters;Dist.inch
is a measurement of distance expressed in inches.I've been thinking about how this could be modeled more generically into a
Measure
mixin:Thus:
This same concept could be applied to other measurements, e.g:
Maybe some more specialized APIs like this would be more humane than the current
sys::Unit
class. :)brian Wed 20 May 2009
There is a difference between modeling units and quantities that have a unit. The unit API is a database of units so that higher layers can agree what things like "meter" actually mean.
When it comes to quantities, what happens if I want Ints or Floats? Or maybe a list of Int, Float, or Decimal? Would I create create a wrapper class for every dimension with methods for every unit? I participated in the units JSR which tried to model units with the type system, and I personally didn't really like the result.
So my only goal is to define a normalized representation for what a Unit is so that higher levels can share. How you attach that as meta-data to the values is an application layer issue.
jodastephen Thu 21 May 2009
This is a topic that has come up a lot, so we shouldn't see it as solved yet.
Personally, I'd like to see a new type of literal for units and quantities. I haven't worked out the details, but I think:
As I say, its and outline concept. The tricky part is the using statement (or similar) that causes the compiler to allow specific literal suffixes.
The nice part is that it removes the need for dedicated Duration literals. So, its a unification proposal.
qualidafial Thu 21 May 2009
We could change all the
sys::Decimal
fields and method args tosys::Num
if that makes a difference.I assume you're talking about object overhead? In practice this has never been a problem, and the ability to combine measurements instead of numbers has simplified things like adding measurements of different units:
My only complaint so far has been Java doesn't allow operator overloading, so things like
a*b+c
have to be coded asa.mult(b).plus(c)
which is noisy (and a little annoying).Which JSR?
You probably also want a
literal
keyword or annotation on just the constructors that make sense as literals, so that ctors likemake
doesn't get dragged into the mix:On another note, I'm wondering if the compiler is going to treat these two expressions the same:
For commutative math functions like
*
it may make sense for the compiler to try the operands in the opposite order if the second operand doesn't fit the method argument type for the first operand.brian Thu 21 May 2009
I was more talking about the 60 different quantities the unit database has already. But object overhead has an impact too (especially if you box to Num). In my field, units are almost always attached as meta-data and you never code with a specific unit directly (time aside). Plus I typically work with millions, if not billions of points of data, and overhead is pretty important to me. This is why it is common with tabular data to attach the unit meta-data to the column, not each individual value.
I think the main point here, is that how units are used is very much an application layer issue. I am not sure there is a good generic solution, in the mean-time my only goal is to created a normalized representation for the definition of a unit (which is actually immensely useful regardless of how the higher layers are implemented).
JSR 275
The compiler does not do this today. It seems reasonable for
+
or*
, although it definitely would muddy the elegance of operators mapping directly to methods.qualidafial Thu 21 May 2009
The lack of slot name overloading also makes things like this impossible:
You cannot have both because slot name overloading is not allowed, unless there is some other way to make a method into a math function. Maybe facets can help us here:
KevinKelley Thu 21 May 2009
This units thing sounds like a really good first test for the
DSL
proposal. Rather than trying to forcefit ring algebras and all that whatnot into the base language, write a DSL that understands all the proper associativities, with its own syntax; have a language feature for communicating back and forth to it.Core Fan shouldn't be trying to provide syntax for everything from furlongs to string theory; but if that DSL thing works right it can cleanly do all that anyway.
brian Thu 21 May 2009
It isn't impossible, you just have to type your method using
Obj
and give up static type checking.tompalmer Thu 21 May 2009
I still think it might be good to allow operator overloading without slot name overloading. For example, allow
*
to map to anything starting withmult
(if the next letter isn't a lowercase letter?), and apply normal overloading rules from Java/C++ at that point. Or maybe a variation on that theme.As I've mentioned before, this (operator overloading) is already supported in Fan for special cases:
get
andslice
, andmake
andfromStr
.brian Thu 21 May 2009
I like that, although I would formalize the naming rule to be something like shortcut+typename:
tompalmer Thu 21 May 2009
I've thought the same, but I was wondering how pod names would play in. Just leave them out? The chance of needing to overload to two types with the same name from different pods seems slim. And if you really needed it, maybe you'd just need to work around by using standard calling conventions rather than operators? That might cover things well enough.
qualidafial Thu 21 May 2009
I was wondering about pod names too. Using facets would remove ambiguity, I think:
Using facets the compiler magic could be explicit instead of intrinsic in the naming convention:
I agree, only
+
and*
would make sense for this. What if we used a facet so classes can opt in to this behavior case by case?qualidafial Thu 21 May 2009
I'm going to go ahead and start a little project on bitbucket for this. I'll be naming the pod
measure
for lack of a better idea. (suggestions welcome)I will need a few things from Fan to make these work elegantly:
div
vsdivDuration
) or by facets (e.g.@mult
,@add
).+
and*
expressions when the first operand doesn't know how to add the second operand, but the second knows how to add the first. A facet seems the right approach to me but I'll take anything that works.sys::Duration
literals are converted to nanoseconds, and do not remember the unit of time used to define them. It would be useful to save that unit of time so it can be queried and referenced directly in code. e.g. a unit of speed is expressed as a unit of distance divided by a unit of time. Right now there is no enum in sys to represent a unit of time so if you guys do not want to support this natively in Fan I will probably have to duplicate Duration to achieve it.brian Thu 21 May 2009
I am for this feature, although I prefer the explicit naming by type - this is a case where the convention should be the rule. What we've to hack around it in DateTime and Duration is ugly.
I am not ready to support this feature, at the very least I need to think it about it a while. Although, since it won't ever work for
-
or/
, I am not sure it really matters, the lhs will always have to be the type further down the dependency chain.It doesn't remember, in practice is hardly ever matters because things get rounded to the whole unit:
I don't think this belongs on the radar for 1.0
jodastephen Thu 21 May 2009
And unfortunately this can be wrong when correctly evaluating dates and times. 1 minute is not necessarily the same as 60 seconds (leap seconds). Similarly for 1 day is not necessarily 24 hours (daylight savings).
As such, have a literal form that creates a Period (Joda-Time/JSR-310 terminology) that does know and remember what you created is a useful thing.
tompalmer Thu 21 May 2009
Joda-Time isn't always as easy to work with as I'd like (sorry -- though I still like it much better than java.util.Calendar of course), but it teaches well the idea of using semantic terminology at the human level, when the human level is what matters (which I find to be often the case).
jodastephen Thu 21 May 2009
I'd love to hear why and what difficulties you've faced with Joda-Time so I don't repeat the mistakes! But lets discuss it privately or on the Joda list, not here!
qualidafial Thu 21 May 2009
I've posted an initial
measure
pod to bitbucket:http://bitbucket.org/qualidafial/fan-measure/
So far there's just
Dist
andDistUnit
, I'll add the other units but this is a decent start.tompalmer Fri 22 May 2009
Sorry. Bad form on my part. Problem will be that it's been a year since I last used it. If I get a chance to take a look again, I'll see if I can provide meaningful feedback in a more appropriate venue.
My main point here (lost in my poorly chosen comment) was definite agreement on the importance of being able to deal with time in terms of the units we use in human language.