Haskell arithmetic operations and DB persistence of arbitrary/fixed precision numbers
Asked Answered
P

1

7

As a Haskell (GHC platform) beginner I bumped into a problem of dealing with data types and arithmetic operations related to business domain that involves currency/money operations and I'm looking for a solution.

I'm developing application that is supposed to interface with (independent) accounting module (via web services) while at the same time having a (web) user interface for ad-hoc data entry which is stored in a separate database (PostgreSQL).

I'm coming from C#/F# environment and System.Decimal covers all the core needs there. Please correct me if I'm wrong but Haskell does not seem to have an integrated (default) data type that could be considered equivalent.

Ideal choice would be a data type that offers arbitrary precision arithmetic or at least something in the lines of Decimal128 (IEEE 754). The type should support rounding (to nearest, ties away from zero and if possible also ties to even) and the following operations: add, subtract, multiply, divide (ideally also square/root). Conversions between types should also be supported.

From what I've managed to find there are two Haskell modules on Hackage that are supposed to perform calculations exactly - Data.Fixed and Data.Decimal (by the way is there any way of creating custom literals in Haskell - e.g. to copy 123.45m from F# ?). At least the latter would as far as I can tell (after a quick test) enables most of what I've described in previous paragraph, but when I add a DB (PostgreSQL via Persistent/HDBC) and a web framework (YESOD) to the mix things don't look so peachy. The support there seems to be lacking.

Is there any other combination that enables what I've described end-to-end (data entry => data processing => storage) with minimal friction (e.g. manual casting from string after loading from DB seems odd having a really strongly typed language) and without the loss of precision (any pointers welcome)?

Parental answered 30/8, 2012 at 17:10 Comment(8)
You don't need custom literals, a fractional literal 123.45 stands for fromRational (2469 % 20) and which fromRational is called is determined by the type. The type is determined by the context (possibly a type signature). Since Rational exactly handles rational numbers (within the memory limits imposed by the machine resp. GMP), there's no need for typed literals.Tunicle
I find custom literals less cumbersome to use and more esthetically pleasing - leading to code that is easier to read - seeing it for the first time I believe comprehension of 123.45m would be easier than 12345 % 100, 2469 % 20 is even harder to link with the defining business rule (but I guess it could just be the matter of personal preference, especially after being used to it for a while).Parental
No, you just use the literal 123.45. The conversion to a rational and then to a value of the required type is done by the implementation. You don't need to bother with that. The only thing a literal of the form 123.45m would add is that the human reader of the code immediately can see the type - if (s)he knows the meaning of the suffix.Tunicle
But doesn't using literals in the way you suggested mean an implicit usage of Double data type and consequently the loss of precision for example: let a = 0.1 + 0.2 + 0.03 - doesn't further references to a mean using value 0.33000000000000007?Parental
No, that's the point, in Haskell, it means the implicit use of Rational, not of Double.Tunicle
Thanks! I did some experimenting with GHCi before that and just assumed that literals of this type always get evaluated to Double immediately, I see now that those were two different contexts.Parental
@CMZ3Z3G6P3: That would be a matter of "defaulting"--your let a = ... above defines a polymorphic value, but only concrete types can actually be evaluated. If nothing constrains the type of such an expression, it will indeed default to Double. Haskell has no implicit conversion between numeric types whatsoever, so a single concrete type will disambiguate every calculation it touches, even indirectly. Thus, the GHCi prompt is often the only place you'll ever see type defaulting occur.Tusk
@DanielFischer: You should post your comment as an answer, since it seems to resolve the question.Satirical
D
3

I am making a Yesod application, and using Database.Persist. For my purposes, I'm going to make a newtype wrapping over a Data.Fixed value, and use an Int64 field, like so:

newtype Dollars = Dollars { unDollars :: Centi } deriving (Eq, Num)

instance PersistField Dollars where
   toPersistValue = PersistInt64 . fromIntegral . fromEnum . unDollars
   fromPersistValue (PersistInt64 c) = Right . Dollars . toEnum . fromIntegral $ c
   fromPersistValue x = Left . Text.pack $ "Expected Int64 counting cents, got: " ++ show x
Deliquesce answered 30/5, 2014 at 22:5 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.