如何模拟货币之间兑换货币的货币,货币和银行?

Ram*_*eka 13 haskell types

嘿所以我正在阅读关于Java中类型驱动开发的这篇文章.我无法学习Java类型,所以我尝试在Haskell中编写它.但是,我有两个问题:

  1. 我不知道如何实现货币与实际货币之间的差异.起初我认为货币只是货币的类型(我认为这是有意义的),像这样data Dollar = Dollar Double,价值就像Dollar 4.0货币,Dollar类型就是货币.我认为Dollar :: Double -> Dollar这将是未出口的东西.
  2. 这导致了一个问题,我无法建立一个交换金钱的银行.我在想类似的东西exchange :: (Money a, Money b) =>[ExchangeRate] -> a -> b.然后银行只是一个包含ExchangeRates集合的对象,但我不知道ExchangeRate是什么类型.

我到目前为止的代码是:

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
Run Code Online (Sandbox Code Playgroud)

编辑:我仍然想要这样的安全:buyAmericanBigMac :: Dollar -> (BigMac, Dollar).

cch*_*ers 20

首先要注意,为了安全起见,exchange应该有类型

exchange :: (Money a, Money b) => [ExchangeRate] -> a -> Maybe b
Run Code Online (Sandbox Code Playgroud)

因为如果您没有ab在您的费率列表中,您将无法返回任何内容.

因为ExchangeRate我们可以使用:

newtype ExchangeRate = Rate { unrate :: (TypeRep, Double) }
  deriving Show
Run Code Online (Sandbox Code Playgroud)

TypeRep是一种独特的"指纹".你可以得到一个TypeRep通过调用typeOf上的东西用Typeable实例.使用这个类,我们可以编写一个类型安全的汇率查找:

findRate :: Typeable a => [ExchangeRate] -> a -> Maybe Double
findRate rates a = lookup (typeOf a) (map unrate rates)
Run Code Online (Sandbox Code Playgroud)

然后我们可以实现你的交换功能:

exchange :: forall a b. (Money a, Money b) => [ExchangeRate] -> a -> Maybe b
exchange rates a = do
  aRate <- findRate rates a
  bRate <- findRate rates (undefined :: b)

  return $ money (bRate * (amount a / aRate))
Run Code Online (Sandbox Code Playgroud)

在这里我们使用ScopedTypeVariables扩展,所以我们可以写undefined :: b(注意我们需要写forall a b.这个以便工作)

这是一个最小的工作示例.而不是[ExchangeRate]我使用了HashMap(它更快,并阻止用户组合不属于一起的交换率).

{-# LANGUAGE ScopedTypeVariables #-}
{-# LANGUAGE DeriveDataTypeable #-}

module Exchange
  ( Dollar
  , Franc
  , exchange

  , sampleRates
  , sampleDollars
  ) where

import Data.HashMap.Strict as HM
import Data.Typeable

class Typeable m => 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

newtype Dollar = Dollar Double
  deriving (Show, Eq, Typeable)

instance Money Dollar where
  money = Dollar
  amount (Dollar a) = a

newtype Franc = Franc Double
  deriving (Show, Eq, Typeable)

instance Money Franc where
  money            = Franc
  amount (Franc a) = a

newtype ExchangeRates = Exchange (HashMap TypeRep Double)
  deriving Show

findRate :: Typeable a => ExchangeRates -> a -> Maybe Double
findRate (Exchange m) a = HM.lookup (typeOf a) m

exchange :: forall a b. (Money a, Money b) => ExchangeRates -> a -> Maybe b
exchange rates a = do
  aRate <- findRate rates a
  bRate <- findRate rates (undefined :: b)

  return $ money (bRate * (amount a / aRate))

sampleRates :: ExchangeRates
sampleRates = Exchange $ HM.fromList
  [ (typeOf (Dollar 0), 1)
  , (typeOf (Franc 0) , 1.2)
  ]

sampleDollars :: Dollar
sampleDollars = Dollar 5
Run Code Online (Sandbox Code Playgroud)

然后你就可以写了

> exchange sampleRates sampleDollars :: Maybe Franc
Just (Franc 6.0)
Run Code Online (Sandbox Code Playgroud)

正如其他人提到的那样,Double因为你可以得到浮点错误,所以不太合适.如果你用真钱做任何事,我建议使用科学.

  • 使用像"Double"这样的浮点表示可能会导致财务问题出现严重问题(浮点错误可能会导致非常重要的错误,这些错误肯定会超过一堆操作).我建议使用固定点表示,因此您可以将数字精确地表示为分数(如果用例需要它,则可以表示分数的一小部分).Hackage上至少有一个用于定点算术的软件包(我相信还有更多). (4认同)

Sib*_*ibi 7

不,不要使用类型类.让我们从基础开始:

那么,您想表示不同的货币类型?让我们使用一个简单的代数数据类型:

data CurrencyType = Dollar | Franc deriving (Show)
Run Code Online (Sandbox Code Playgroud)

你想代表钱,再次使用简单的数据类型:

data Money = Money {
      amount :: Double,
      mType :: CurrencyType
    } deriving (Show)
Run Code Online (Sandbox Code Playgroud)

ghci中的一些演示:

*Main> let fiveDollars = Money 5 Dollar
*Main> fiveDollars
Money {amount = 5.0, mType = Dollar}   
Run Code Online (Sandbox Code Playgroud)

现在,您希望能够将货币从一种货币类型转换为另一种货币类型.这又可以通过一个简单的功能来实现:

convertMoney :: CurrencyType -> Money -> Money
convertMoney Dollar money = undefined -- logic for Converting money to Dollar                
convertMoney Franc money = undefined -- logic for converting money to Franc  
Run Code Online (Sandbox Code Playgroud)

进入类型类的一般规则是当我想要表示一些具有明确定义的法则的特定抽象时.对于大多数情况,简单的数据类型和在它们上运行的函数将是一个很好的例子.


根据您的评论进行更新:如果您希望能够声明自己的资金类型,那么您可以遵循以下方法:

data CurrencyType a = CurrencyType a deriving (Show)

data Dollar = Dollar deriving (Show)

data Money a = Money Double (CurrencyType a) deriving (Show)
Run Code Online (Sandbox Code Playgroud)

演示ghci:

?> let fiveDollars = Money 5 (CurrencyType Dollar)
?> fiveDollars
Money 5.0 (CurrencyType Dollar)
Run Code Online (Sandbox Code Playgroud)

现在假设你要定义另一种货币Franc.然后只为它定义一个数据类型:

data Franc = Franc deriving (Show)
Run Code Online (Sandbox Code Playgroud)

然后你可以用它来定义钱:

?> let fiveFranc = Money 5 (CurrencyType Franc)
?> fiveFranc
Money 5.0 (CurrencyType Franc)
Run Code Online (Sandbox Code Playgroud)

>> I can't write a function that only takes Dollars at compile time.

好吧,你可以.

convertFromDollar :: Money Dollar -> Money Franc
convertFromDollar x = undefined -- Write your logic here     
Run Code Online (Sandbox Code Playgroud)

  • 但我发现这有两个问题.1,别人不能申报他们自己的钱(虽然我不确定这是多么有问题).2,更重要的是我不能编写一个在编译时只需要Dollars的函数.这增加了一整类错误. (4认同)