我将如何处理以下情况? 我有一个DU(例如货币)和一些记录类型。 现在对于记录类型字段,我要求给定实例的实际值应该是相同的案例标识符(或者在Haskell中使用相同的值构造函数)
type Currency =
| USD of decimal
| EUR of decimal
type PositionalData = {
grossAmount: Currency;
pos1: Currency;
pos2: Currency;
}
例如以下内容有效
let valid = {
grossAmount = USD 10.0m;
pos1 = USD 7.0m;
pos2 = USD 3.0m;
}
此示例应该无效
let wrong = {
grossAmount = USD 10.0m;
pos1 = USD 7.0m;
pos2 = EUR 3.0m;
^^^^^^^^^^^^^^^^
}
我知道这个特定的例子可以使用测量单位在F#中解决。但很容易想象一个通过该机制无法解决的例子。 所以我想请你考虑一个更通用的答案,而不一定只是解决了代码示例。
期待你的大脑转储; - )
PS:对于所有Haskeleers来说 - 看看ADT(可能与更高级别的kinded类型组合)如何解决这个问题会很有趣。
答案 0 :(得分:4)
“直接”翻译可以
{-# 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 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
提取货币符号(或任何其他数据)的一种方法可能是
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}
答案 1 :(得分:3)
您无法直接比较F#中的DU子类型(另请参阅此答案:https://stackoverflow.com/a/30841893/3929902),但您可以使用这种迂回方式实现它:
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不限于Currency,但可以是任何类型
答案 2 :(得分: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案例,我建议您查看this answer。