Haskell 如何对货币、货币和在货币之间交换货币的银行进行建模?
嘿,我在读Java中的类型驱动开发。我很难找到Java类型,所以我尝试用Haskell编写它。然而,我有两个问题:Haskell 如何对货币、货币和在货币之间交换货币的银行进行建模?,haskell,types,Haskell,Types,嘿,我在读Java中的类型驱动开发。我很难找到Java类型,所以我尝试用Haskell编写它。然而,我有两个问题: 我不知道如何实现货币和实际货币之间的差异。起初,我认为货币只是货币的一种类型(我认为这是有道理的),比如data Dollar=Double,其中Dollar 4.0就是货币,而Dollar就是货币。我认为,Dollar::Double->Dollar不会被输出 这就导致了一个问题,我无法模拟一家兑换货币的银行。我在想类似于交换::(货币a,货币b)=>[ExchangeRate]
data Dollar=Double
,其中Dollar 4.0
就是货币,而Dollar
就是货币。我认为,Dollar::Double->Dollar
不会被输出交换::(货币a,货币b)=>[ExchangeRate]->a->b
。银行只是一个包含汇率集合的对象,但我不知道汇率是什么类型class Money m where
money :: (Money m) => Double -> m
amount :: (Money m) => m -> Double
add :: (Money m) => m -> m -> m
add a b = money $ amount a + amount b
class (Money a, Money b) => ExchangeablePair a b where
newtype Dollar = Dollar Double
deriving (Show, Eq)
instance Money Dollar where
money = Dollar
amount (Dollar a) = a
newtype Franc = Franc Double
deriving (Show, Eq)
instance Money Franc where
money = Franc
amount (Franc a) = a
instance ExchangeablePair Dollar Franc where
编辑:我仍然想要这样的安全性:
buyAmericanBigMac::Dollar->(BigMac,Dollar)
不,不要使用类型类。让我们从基础开始:
那么,您想表示不同的货币类型吗?让我们使用一个简单的algebric数据类型:
data CurrencyType = Dollar | Franc deriving (Show)
data Money = Money {
amount :: Double,
mType :: CurrencyType
} deriving (Show)
要表示货币,请再次使用简单的数据类型:
data CurrencyType = Dollar | Franc deriving (Show)
data Money = Money {
amount :: Double,
mType :: CurrencyType
} deriving (Show)
ghci中的一些演示:
*Main> let fiveDollars = Money 5 Dollar
*Main> fiveDollars
Money {amount = 5.0, mType = Dollar}
λ> let fiveDollars = Money 5 (CurrencyType Dollar)
λ> fiveDollars
Money 5.0 (CurrencyType Dollar)
现在,您希望能够将货币从一种货币类型转换为
另一个这同样可以通过一个简单的函数来实现:
convertMoney :: CurrencyType -> Money -> Money
convertMoney Dollar money = undefined -- logic for Converting money to Dollar
convertMoney Franc money = undefined -- logic for converting money to Franc
对于类型类,我的一般规则是,当我想要表示某个特定的抽象时,它有一些定义良好的规则。对于大多数情况,简单的数据类型和对其进行操作的函数将是一个很好的例子
根据您的评论更新:如果您希望能够申报您自己的货币类型,则可以采用以下方法:
data CurrencyType a = CurrencyType a deriving (Show)
data Dollar = Dollar deriving (Show)
data Money a = Money Double (CurrencyType a) deriving (Show)
ghci中的演示:
*Main> let fiveDollars = Money 5 Dollar
*Main> fiveDollars
Money {amount = 5.0, mType = Dollar}
λ> let fiveDollars = Money 5 (CurrencyType Dollar)
λ> fiveDollars
Money 5.0 (CurrencyType Dollar)
现在,假设您想定义另一种货币法郎
。然后只需为其定义一个数据类型:
data Franc = Franc deriving (Show)
然后你可以从中定义金钱:
λ> let fiveFranc = Money 5 (CurrencyType Franc)
λ> fiveFranc
Money 5.0 (CurrencyType Franc)
>我无法编写编译时只需要美元的函数。
嗯,你可以
convertFromDollar :: Money Dollar -> Money Franc
convertFromDollar x = undefined -- Write your logic here
首先要注意的是,为了安全起见,
exchange
应该有
exchange :: (Money a, Money b) => [ExchangeRate] -> a -> Maybe b
因为如果您的费率列表中没有a
或b
,您将无法返回任何内容
对于ExchangeRate
,我们可以使用:
newtype ExchangeRate = Rate { unrate :: (TypeRep, Double) }
deriving Show
TypeRep
是类型的唯一“指纹”。您可以通过调用带有Typeable
实例的东西上的typeOf
来获得TypeRep
。使用该类,我们可以编写汇率的类型安全查找:
findRate::Typeable a=>[ExchangeRate]->a->可能是双精度的
findRate速率a=查找(类型a)(映射未速率)
然后我们可以实现您的交换功能:
convertMoney :: CurrencyType -> Money -> Money
convertMoney Dollar money = undefined -- logic for Converting money to Dollar
convertMoney Franc money = undefined -- logic for converting money to Franc
exchange::for all a b。(货币a,货币b)=>[汇率]->a->可能是b
汇率a=do
aRate我将如何在Haskell中实现它,基于我在PHP中的工作方式:
module Money where
-- For instance Show Money
import Text.Printf
-- Should perhaps be some Decimal type
type Amount = Double
-- Currency type
data Currency = Currency { iso4217 :: String } deriving Eq
instance Show Currency where
show c = iso4217 c
-- Money type
data Money = Money { amount :: Amount, currency :: Currency }
instance Show Money where
show m = printf "%0.2f" (amount m) ++ " " ++ show (currency m)
-- Conversion between currencies
data BasedRates = BasedRates { base :: Currency, rate :: Currency -> Amount }
type CrossRates = Currency -> Currency -> Amount
makeCrossRatesFromBasedRates :: BasedRates -> CrossRates
makeCrossRatesFromBasedRates (BasedRates { base=base, rate=rate }) =
\ fromCurrency toCurrency -> rate toCurrency / rate fromCurrency
convert :: CrossRates -> Currency -> Money -> Money
convert crossRates toCurrency (Money { amount=amount, currency=fromCurrency })
= Money { amount = crossRates fromCurrency toCurrency * amount, currency=toCurrency }
-- Examples
sek = Currency { iso4217 = "SEK" }
usd = Currency { iso4217 = "USD" }
eur = Currency { iso4217 = "EUR" }
sekBasedRates = BasedRates {
base = sek,
rate = \currency -> case currency of
Currency { iso4217 = "SEK" } -> 1.0000
Currency { iso4217 = "USD" } -> 6.5432
Currency { iso4217 = "EUR" } -> 9.8765
}
crossRates = makeCrossRatesFromBasedRates sekBasedRates
usdPrice = Money { amount = 23.45, currency = usd }
sekPrice = convert crossRates sek usdPrice
eurPrice = convert crossRates eur usdPrice
我倾向于将货币视为衡量单位,并以F#风格行事。另外,不要使用浮点。但我发现这有两个问题。1、其他人不能声明他们自己的货币类型(尽管我不确定这有多大问题)。更重要的是,我不能编写一个在编译时只需要美元的函数。这就增加了一整类错误。@RamithJayatilleka我更新了我的答案,以包括您的其他用例。好的,谢谢。在看了Gallais的答案和链接后,我明白了我应该通过虚拟货币来赚钱。这现在是有道理的。@RamithJayatilleka:这是一个好方法。然而,当你想要一套动态的汇率时,你会遇到一些困难。(就像你的[ExchangeRate]
)我现在正在写一个答案来处理它,但有点尴尬。@TikhonJelvis是的,我刚刚碰到了那个问题。我尝试了data Rate a b=(货币a,货币b)=>Rate Double
,因为我认为在类型中编码货币是有意义的。但我不知道如何在货币不同的情况下对汇率集合进行编码。*嘿,我从库拉认识你!哇,我不知道有可打字的东西存在。谢谢。使用像Double
这样的浮点表示法可能会导致一些严重的财务问题(浮点错误可能会导致非常重要的不准确,这肯定会在一系列操作中累积)。我建议改为使用定点表示法,这样您就可以精确地将数字表示到美分(如果用例需要的话,也可以是美分的一小部分)。至少有一个关于定点算术的黑客软件包(我相信还有更多)。