Copyright | © 2018 Phil de Joux © 2018 Block Scope Limited |
---|---|
License | MPL-2.0 |
Maintainer | Phil de Joux <phil.dejoux@blockscope.com> |
Stability | experimental |
Safe Haskell | None |
Language | Haskell2010 |
For encoding and decoding newtype rationals as scientific with a fixed number of decimal places.
- newtype DecimalPlaces = DecimalPlaces Int
- data ViaSci n where
- ViaSci :: (DefaultDecimalPlaces n, Newtype n Rational) => n -> ViaSci n
- class DefaultDecimalPlaces a where
- dpDegree :: DecimalPlaces
- showSci :: DecimalPlaces -> Scientific -> String
- fromSci :: Scientific -> Rational
- toSci :: DecimalPlaces -> Rational -> Scientific
- deriveDecimalPlaces :: DecimalPlaces -> Name -> Q [Dec]
- deriveJsonViaSci :: Name -> Q [Dec]
- deriveCsvViaSci :: Name -> Q [Dec]
Usage
Let's say we have a latitude that is a newtype
Rational
number but we
want it to be encoded to JSON with a fixed number of decimal places.
>>>
newtype Lat = Lat Rational deriving (Eq, Ord, Show)
Types going ViaSci
also need to be instances of DefaultDecimalPlaces
and
Newtype
.
>>>
:{
instance DefaultDecimalPlaces Lat where defdp _ = DecimalPlaces 8 instance Newtype Lat Rational where pack = Lat unpack (Lat a) = a instance ToJSON Lat where toJSON x = toJSON $ ViaSci x instance FromJSON Lat where parseJSON o = do ViaSci x <- parseJSON o; return x :}
>>>
let x = 1122334455667788 % 10000000000000000
>>>
fromRational x
0.1122334455667788>>>
toSci (DecimalPlaces 8) x
0.11223345
When having to check numbers by hand, a fixed decimal is more familiar than a ratio of possibly large integers.
>>>
encode x
"{\"numerator\":280583613916947,\"denominator\":2500000000000000}">>>
encode (Lat x)
"0.11223345"
With too few decimal places, the encoding will be lossy.
>>>
decode (encode x) == Just x
True>>>
decode (encode (Lat x)) == Just (Lat x)
False>>>
let Just (Lat y) = decode (encode (Lat x)) in fromRational y
0.11223345
Similarly for CSV.
>>>
:{
instance ToField Lat where toField = toField . ViaSci instance FromField Lat where parseField c = do ViaSci x <- parseField c; return x :}
>>>
Csv.encode [("A", Lat x)]
"A,0.11223345\r\n">>>
Csv.decode Csv.NoHeader (Csv.encode [("B", Lat x)]) == Right (fromList [("B", Lat x)])
False>>>
Csv.decode Csv.NoHeader (Csv.encode [("C", Lat x)]) == Right (fromList [("C", Lat . fromSci . toSci (DecimalPlaces 8) $ x)])
True
Decimal Places
newtype DecimalPlaces Source #
A positive number of decimal places.
An intermediate type used during encoding to JSON with aeson
and during
encoding to CSV with cassava
. It's also used during decoding.
The original type, a newtype Rational
, goes to and fro via
scientific so that the rational value can be encoded as a scientific
value with a fixed number of decimal places.
ViaSci :: (DefaultDecimalPlaces n, Newtype n Rational) => n -> ViaSci n |
Eq n => Eq (ViaSci n) Source # | |
Ord n => Ord (ViaSci n) Source # | |
Show n => Show (ViaSci n) Source # | |
(DefaultDecimalPlaces n, Newtype n Rational) => ToJSON (ViaSci n) Source # | |
(DefaultDecimalPlaces n, Newtype n Rational) => FromJSON (ViaSci n) Source # | |
(DefaultDecimalPlaces n, Newtype n Rational) => FromField (ViaSci n) Source # | |
(DefaultDecimalPlaces n, Newtype n Rational) => ToField (ViaSci n) Source # | |
class DefaultDecimalPlaces a where Source #
A default number of decimal places for a type.
defdp :: a -> DecimalPlaces Source #
dpDegree :: DecimalPlaces Source #
A choice of 8 decimal places for decimal degrees is just a bit more than a mm at the equator and less elsewhere.
- 1.1132 mm at the equator
- 1.0247 mm at 23° N/S
- 787.1 µm at 45° N/S
- 434.96 µm at 67° N/S
showSci :: DecimalPlaces -> Scientific -> String Source #
Shows a Scientific
value with a fixed number of decimal places.
>>>
let x = 0.1122334455667788
>>>
showSci (DecimalPlaces 16) x
"0.1122334455667788">>>
showSci (DecimalPlaces 8) x
"0.11223345">>>
showSci (DecimalPlaces 4) x
"0.1122">>>
showSci (DecimalPlaces 1) x
"0.1">>>
showSci (DecimalPlaces 0) x
"0">>>
showSci (DecimalPlaces (-1)) x
"0">>>
showSci (DecimalPlaces 32) x
"0.11223344556677880000000000000000"
Conversions
fromSci :: Scientific -> Rational Source #
From Scientific
exactly to Rational
.
>>>
let x = 0.1122334455667788
>>>
fromSci x
4043636029064415 % 36028797018963968>>>
x == fromRational (fromSci x)
True
toSci :: DecimalPlaces -> Rational -> Scientific Source #
To Scientific
from Rational
as near as possible up to the given number
of DecimalPlaces
with rounding.
>>>
let x = 1122334455667788 % 10000000000000000
>>>
toSci (DecimalPlaces 8) x
0.11223345>>>
x == toRational (toSci (DecimalPlaces 8) x)
False>>>
x == toRational (toSci (DecimalPlaces 16) x)
True>>>
x == toRational (toSci (DecimalPlaces 32) x)
True
Deriving instances with Template Haskell
deriveDecimalPlaces :: DecimalPlaces -> Name -> Q [Dec] Source #
Taking a number of decimal places from the given DecimalPlaces
newtype,
derives an instance of DefaultDecimalPlaces
.
>>>
deriveDecimalPlaces (DecimalPlaces 8) ''Lat
...
deriveCsvViaSci :: Name -> Q [Dec] Source #
Similar to deriveJsonViaSci
but for instances of ToField
and FromField
.
>>>
deriveCsvViaSci ''Lat
...