Closed jameysharp closed 9 years ago
I hate to say it, but this is a terrible idea: it breaks the unit abstraction. I'm actually totally fine with Qu Length l (Int -> Float)
. I'm not fine at all, however, with fmap (+5) some_quantity
-- the (+5)
has no units specified! This allows you to observe what units are used internally to store a quantity, something that should never be visible (ignoring rounding errors.... which were one of the motivating factors for LCSUs, of course).
We really want to allow only operations parametric in units to be fmap
ped. Something like fmap (>0) some_quantity
is unit-safe. But fmap (>1) some_quantity
is not. I don't think the type system is up to distinguishing these!
On the other hand, I would be happy with adding these instances to Data.Metrology.Unsafe
if you want. I agree that they're useful -- they're just unsafe! Or, much better, I'd love to see a way to make these kinds of operations safe, but I haven't the foggiest clue of how to do so.
If you want a matrix of quantities, is there a way of using Data.Metrology.Vector
to your advantage?
I had the feeling there was some safety issue but I couldn't quite see what it was. Thanks for the examples! That's why I filed an issue instead of a pull request...
I considered suggesting putting the instances in Data.Metrology.Unsafe but I thought it was too weird to deal with the instance export rules. I guess since Unsafe.hs isn't imported by any other module in the units package, it's OK to put the instances there because they won't be accidentally re-exported that way. Is that right?
It's possible to build safe interfaces on top of these. For instance, I'm generating C code using the Ivory EDSL, which has a SafeCast typeclass for type conversions that preserve value. A function like this is safe, I think, and if it weren't for the Fractional constraint you could tell it's safe from the type alone:
unitCast :: (SafeCast from to, ConvertibleLCSUs d l1 l2, Fractional to) => Qu d l1 from -> Qu d l2 to
unitCast = convert . fmap safeCast
Inconveniently, a module providing a safe interface like this would effectively re-export the unsafe Functor etc. instances. So maybe that's still the wrong plan.
I don't understand the vector-space library, but it's clearly missing a lot of functionality I need that's in linear. That said, writing the relevant instances for linear looks about equivalent to writing them for vector-space.
Perhaps it's possible to build a units
interface on top of linear
, instead of vector-space
. I didn't consider doing this, so it's not as if I've preferred vector-space
over linear
-- it's just what I was familiar with at the time. It is sad that these interactions have to be "built in" the units
library. Something openly compositional would be far better. But we're not there, somehow.
In answer to your questions: You bring up a good point about re-export of instances. Putting unsafe instances in Data.Metrology.Unsafe
effectively prohibits a client from building a safe interface over Data.Metrology.Unsafe
. Yuck! I'm open to Data.Metrology.UnsafeInstances
as a separate module, just so that there's a canonical location for these instances, preventing possible orphan-instance conflicts down the road.
Here's an idea for how you can get your Functor
instance while remaining safe, demonstrated easiest by example:
module MySafeInterface ( safeOperation ) where
import Data.Metrology.Unsafe
newtype WQu d l n = Wrap { unwrap :: Qu d l n }
instance Functor (WQu d l) where
fmap f (Wrap (Qu x)) = Wrap (Qu (f x))
safeOperation :: (...) => Qu d l n1 -> Qu d l n2
safeOperation x = unwrap $ do_amazing_things_that_require_Functor $ Wrap x
If your safeOperation
needs to take structures containing quantities, you can always use Data.Coerce.coerce
to do the wrapping and unwrapping.
This is far from pretty, but it just might work. Do you think this would fit your use-case?
Oh hey, that might work! And what would you think of exporting something like WQu from Data.Metrology.Unsafe? I might suggest a less-wieldy name like UnsafeQu for emphasis though.
As long as Qu and UnsafeQu have the same kind (as they do in your example), I think I can parameterize my vector datatypes over whether the quantities are safe or not. Then at least it will be obvious from the types when I need to be careful.
I should probably have mentioned that the vector and matrix types in question are in some sensor fusion via Kalman filtering code that I'm working on. Those types are defined in estimator:Numeric.Estimator.Model.SensorFusion, in case you want to have a look.
I think it has turned out that getting units properly encoded in my types is too complicated for now (I haven't even mentioned some other aspects I think are nastier than these) so I will have to come back to this. But at least there's a plausible direction to try when I have some spare time. I don't mind if you'd prefer to close this issue until then. Thanks for your insights!
I'm going to leave this open as a suggestion to put UnsafeQu
in Data.Metrology.Unsafe
. And thank you!
@jameysharp may be interested in the new branch linear
, as described in #45. I would be happy with a linear
interface to units
, but I don't have the expertise (or time, frankly) to build it out. (Truth be told, I don't think it would take a ton of time... but I don't have the time in which to gain the expertise!)
Maybe this is a terrible idea; I'd appreciate feedback. I think I want instances of all the Functor-related classes for
Qu d l
. Some uses would be semantically strange but still useful; for instance,Qu Length l (Int -> Float)
is a funny-looking type but I guess it's a function that, given a length as an Int, returns a (possibly different) length as a Float. Obviously most of the library can't operate on such a type, but you'd use it with Applicative's<*>
as an intermediate value on the way to something that the library can operate on.My immediate use cases are that:
fmap fromIntegral
to convert them to Float and then maybe pick a new LCSU.