How would I approach the following situatiuon?
I have a DU (for example a Currency) and some record type.
Now for the record type fields I have the requierements that the actual values for a given instance should al be of the same case identifier (or in Haskell the same value constructor)
type Currency =
| USD of decimal
| EUR of decimal
type PositionalData = {
grossAmount: Currency;
pos1: Currency;
pos2: Currency;
}
for example the following is valid
let valid = {
grossAmount = USD 10.0m;
pos1 = USD 7.0m;
pos2 = USD 3.0m;
}
where as this example should be invalid
let wrong = {
grossAmount = USD 10.0m;
pos1 = USD 7.0m;
pos2 = EUR 3.0m;
^^^^^^^^^^^^^^^^
}
I know this particular example can be solved in F# using Units of Measurement. But its easy to envision an example that is not solvable via that mechanism. So I like to ask you to consider a more generic answer and not necessarily one that just solves the give code example.
Looking forward to your brain dumps ;-)
PS: for all the Haskeleers around - It would be interesting to see how ADT (maybe in combination wit higher kinded types) can solve that.
A "direct" translation could be
{-# LANGUAGE GADTs, DataKinds, KindSignatures #-}
data Currency = USD | EUR deriving Show
-- We use `Currency` values to create `Amount` types
-- read about types in Haskell ([Kinds][1]: *, * -> *, ...)
-- here we fix one * to be Currency
data Amount :: Currency -> * where
-- Data constructor, take one float and return some Amount
Amount :: Float -> Amount a
-- Extract the specific currency symbol require extra effort
instance Show (Amount a) where
show (Amount k) = show k
-- Many amounts (same currency)
-- `a` restrict `a1` and `a1` to have the same type => the same currency
data PData a = PData { a1 :: Amount a
, a2 :: Amount a
} deriving Show
-- Helpers
usd :: Float -> Amount USD
usd = Amount
eur :: Float -> Amount EUR
eur = Amount
main = do
print $ PData (usd 3) (usd 4) -- OK
print $ PData (eur 3) (eur 4) -- OK
print $ PData (eur 3) (usd 4) -- KO, Couldn't match type 'USD with 'EUR
(1) https://wiki.haskell.org/Kind
On the other hand, @TheInnerLight remember me you can use phantom types
-- NOTE: this is not a "direct translation" since currencies are not
-- enumerated and is slightly different
data USD = USD
data EUR = EUR
data Amount c = Amount { amount :: Float }
instance Show (Amount c) where
show (Amount a) = show a
data PData c = PData { c1 :: Amount c
, c2 :: Amount c }
deriving Show
usd :: Float -> Amount USD
usd = Amount
eur :: Float -> Amount EUR
eur = Amount
main = do
print $ PData (usd 3) (usd 4) -- OK
print $ PData (eur 3) (eur 4) -- OK
print $ PData (eur 3) (usd 4) -- KO, Couldn't match type 'USD with 'EUR
One way to extract currency symbols (or any other data) could be
class Symbol c where symbol :: c -> String
instance Symbol USD where symbol _ = "USD"
instance Symbol EUR where symbol _ = "EUR"
instance Symbol c => Show (Amount c) where
show s@(Amount a) = sym undefined s ++ " " ++ show a
where sym :: Symbol c => c -> Amount c -> String
sym k _ = symbol k
printing
PData {c1 = USD 3.0, c2 = USD 4.0}
PData {c1 = EUR 3.0, c2 = EUR 4.0}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With