4 ms·
Haskell has a few libraries to do this. I particularly like unittyped[1], which not only keeps track of units but also converts among compatible ones automatica
by tikhonj 12y ago
Haskell has a few libraries to do this. I particularly like unittyped[1], which not only keeps track of units but also converts among compatible ones automatically. So 1 meter + 1 inch would typecheck and be converted automatically, but 1 meter + 1 second would give you a type error.
The wiki page[2] has a bunch of examples, which I find pretty compelling. The one problem is that error messages are ugly, but they're ugly in a consistent way. You can just ignore the ugliness as unnecessary noise.
*Main> (1 meter / second) * 5 second
5.0 m/s⋅s
*Main> 2 meter + (1 meter / second) * 5 second
7.0 m
One cute thing is that prefixes like "kilo" are just functions, letting you write things like:
*Main> (42 kilo meter) `as` mile
26.097590073968025 mile
*Main> gallon `as` (cubic (deci meter))
4.546089999999999 dm⋅dm⋅dm⋅#
Haskell is really good at dealing with numeric types in general. For example, it's quite easy for a library to define its own types, which then behave just like built-in ones including nice syntax. Unittyped follows this philosophy, letting you use units with things that aren't floats, like rational numbers, meaning you don't have to lose precision.
*Main> (1 % 2) . meter `as` foot
625 % 381 ft
It's a really slick design and manages to give you safety as well as additional expressivity (since units get converted automatically).
[1]: https://hackage.haskell.org/package/unittyped https://hackage.haskell.org/package/unittyped
- berdario 12y agoI'm not sure why, but your examples aren't working with me... 1 *| meter |+| 35 *| centi meter 1.35 m works, but 1 meter fails with <interactive>:42:1: No instance for (Num (Value LengthDimension (U Meter) f0 -> t0)) arising from the literal `1' Possible fix: add an instance declaration for (Num (Value LengthDimension (U Meter) f0 -> t0)) In the expression: 1 In the expression: 1 meter In an equation for `it': it = 1 meter <interactive>:42:3: No instance for (Fractional f0) arising from a use of `meter' The type variable `f0' is ambiguous Possible fix: add a type signature that fixes these type variable(s) Note: there are several potential instances: instance Fractional Double -- Defined in `GHC.Float' instance Fractional Float -- Defined in `GHC.Float' instance Integral a => Fractional (GHC.Real.Ratio a) -- Defined in `GHC.Real' ...plus four others In the first argument of `1', namely `meter' In the expression: 1 meter In an equation for `it': it = 1 meter
- gizmo686 12y agoTry running "import UnitTyped.NoPrelude" first. EDIT: you may also want to start ghci with the "-XNoImplicitPrelude" flag, or explicitly disambiguate operations such as "*" with "UnitTyped.NoPrelude.*" or "Prelude.*"
- berdario 12y agoThanks, but that's not enough. The error seems due to the fact that `meter` is passed as an argument to `1` which is not a function, and indeed I don't see how that could be valid haskell code (unless there's some language flag I should enable, but unfortunately the wiki/docs of unittyped doesn't seem to be comprehensive enough)
- gizmo686 12y agoWeird, it is enough on my machine (running ghc 7.6.3, unittyped 0.1), but unittyped does seem to use a lot of language extensions internally, it is seems possible that it would behave weirdly on different versions. To answer the question of how it is possible, we can start by looking at the types. ":t" is a ghci command to show the type of an expression: ghci> :t 1 1 :: Num a => a This indicates that "1" can be any type that implements the "Num" typeclass. Next, we want to determine what type "1" takes in the expression "1 meter". We can do this with: ghci> let a=1; b=a meter ghci> :t a a :: UnitTyped.Value Double LengthDimension Meter -> UnitTyped.Value Double ('UnitTyped.UnitCons * Length ('UnitTyped.Pos 'UnitTyped.One) 'UnitTyped.UnitNil) Meter We can see that, in the expression "1 meter", "1" is actually a function. Looking at the source code [1], it seems like this is accomplished in a relativly hacky manner: instance (Fractional f, Convertable a b, t ~ Value f a b) => (Prelude.Num (Value f a b -> t)) where fromInteger i x = Prelude.fromInteger i . x (+) = error "This should not happen" (*) = error "This should not happen" abs = error "This should not happen" signum = error "This should not happen" This works because the "+" being defined here is Prelude.+, not UnitTyped.NoPrelude.+ (which is defined seperatly). [1] https://hackage.haskell.org/package/unittyped-0.1/docs/src/UnitTyped-NoPrelude.htm https://hackage.haskell.org/package/unittyped-0.1/docs/src/U...
- eru 12y ago> Haskell is really good at dealing with numeric types in general. I just wish they had based the numbers on mathematical concepts like rings and fields, instead of this weird Num thing.