How to restrict DU / ADT to specific identifiers / value constructors

How can I approach the following situation?
I have a DU (e.g. currency) and some type of record. Now for record type fields, I have requirements that the actual values ​​for a given instance must be 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, valid

let valid = {
    grossAmount = USD 10.0m;
    pos1 = USD 7.0m;
    pos2 = USD 3.0m;
}

where, as this example must be invalid

let wrong = {
    grossAmount = USD 10.0m;
    pos1 = USD 7.0m;
    pos2 = EUR 3.0m;
    ^^^^^^^^^^^^^^^^
}

I know that this specific example can be solved in F # using Units. But it is easy to imagine an example that cannot be resolved using this mechanism. Therefore, I would like to ask you to consider a more general answer, and not necessarily one that simply solves the sample code code.

; -)

PS: Haskeleers - , ADT (, ) .

+4
3

""

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

, @TheInnerLight , phantom

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

( )

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

PData {c1 = USD 3.0, c2 = USD 4.0}
PData {c1 = EUR 3.0, c2 = EUR 4.0}
+4

DU F # ( . : fooobar.com/questions/1593194/...), :

type USD =
    Amount of decimal
type EUR =
    Amount of decimal

type Currency = 
    | USD of USD
    | EUR of EUR

type PositionalData<'T> =
  {
      grossAmount: 'T
      pos1: 'T
      pos2: 'T
  }        

let valid = {
    grossAmount = USD.Amount 10.0m;
    pos1 = USD.Amount 7.0m;
    pos2 = USD.Amount 3.0m;
}

let wrong = {
    grossAmount = USD.Amount 10.0m;
    pos1 = USD.Amount 7.0m;
    pos2 = EUR.Amount 3.0m;
}

, PositionalData ,

+3

, , . , , , .

, , , , F #.

, - :

type PositionalData = {
    grossAmount: decimal;
    pos1: decimal;
    pos2: decimal;
}

type Currency =
    | USD of PositionalData
    | EUR of PositionalData

PositionalData :

type Currency<'a> =
    | USD of 'a
    | EUR of 'a

Haskell .

+3

Source: https://habr.com/ru/post/1647991/


All Articles