detour-via-sci-1.0.0: JSON and CSV encoding for rationals as decimal point numbers.

Data.Via.Scientific

Description

For encoding and decoding newtype rationals as scientific with a fixed number of decimal places.

Synopsis

# 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.

Constructors

 DecimalPlaces Int

Instances

 Source # MethodsshowList :: [DecimalPlaces] -> ShowS # Source # Methods

data ViaSci n where Source #

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.

Constructors

 ViaSci :: (DefaultDecimalPlaces n, Newtype n Rational) => n -> ViaSci n

Instances

 Eq n => Eq (ViaSci n) Source # Methods(==) :: ViaSci n -> ViaSci n -> Bool #(/=) :: ViaSci n -> ViaSci n -> Bool # Ord n => Ord (ViaSci n) Source # Methodscompare :: ViaSci n -> ViaSci n -> Ordering #(<) :: ViaSci n -> ViaSci n -> Bool #(<=) :: ViaSci n -> ViaSci n -> Bool #(>) :: ViaSci n -> ViaSci n -> Bool #(>=) :: ViaSci n -> ViaSci n -> Bool #max :: ViaSci n -> ViaSci n -> ViaSci n #min :: ViaSci n -> ViaSci n -> ViaSci n # Show n => Show (ViaSci n) Source # MethodsshowsPrec :: Int -> ViaSci n -> ShowS #show :: ViaSci n -> String #showList :: [ViaSci n] -> ShowS # Source # MethodstoJSON :: ViaSci n -> Value #toJSONList :: [ViaSci n] -> Value #toEncodingList :: [ViaSci n] -> Encoding # Source # MethodsparseJSON :: Value -> Parser (ViaSci n) # Source # MethodsparseField :: Field -> Parser (ViaSci n) # Source # MethodstoField :: ViaSci n -> Field #

class DefaultDecimalPlaces a where Source #

A default number of decimal places for a type.

Methods

defdp :: a -> 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

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

From Scientific exactly to Rational.

>>> let x = 0.1122334455667788
>>> fromSci x
4043636029064415 % 36028797018963968
>>> x == fromRational (fromSci x)
True


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

Taking a number of decimal places from the given DecimalPlaces newtype, derives an instance of DefaultDecimalPlaces.

>>> deriveDecimalPlaces (DecimalPlaces 8) ''Lat
...


Derives an instance of ToJSON wrapping the value with ViaSci before encoding. Similarly the value is decoded as ViaSci and then unwrapped in the derived instance of FromJSON.

>>> deriveJsonViaSci ''Lat
...


Similar to deriveJsonViaSci but for instances of ToField and FromField.

>>> deriveCsvViaSci ''Lat
...