Haskell 在签名函数中强制使用不同的值构造函数

Haskell 在签名函数中强制使用不同的值构造函数,haskell,types,ghc,dependent-type,data-kinds,Haskell,Types,Ghc,Dependent Type,Data Kinds,我负责货币和货币业务。我希望操作是类型安全的,但我还需要将不同的货币存储在一个集合中,以便搜索它们 这两个目标似乎有冲突 我可以用一个选项类型来实现它,但我在操作中没有类型安全性: type Number=Rational 数据货币=美元|欧元|英镑 数据值=值-数字-货币 --我可以要这个 类型转换率=(货币、货币、数字) 转换率::[转换率] 转换率=[(英镑,欧元,1.2)] --这不是类型安全的,允许对不同的货币求和 sumValue::Value->Value->Value sumVa

我负责货币和货币业务。我希望操作是类型安全的,但我还需要将不同的货币存储在一个集合中,以便搜索它们

这两个目标似乎有冲突

我可以用一个选项类型来实现它,但我在操作中没有类型安全性:

type Number=Rational
数据货币=美元|欧元|英镑
数据值=值-数字-货币
--我可以要这个
类型转换率=(货币、货币、数字)
转换率::[转换率]
转换率=[(英镑,欧元,1.2)]
--这不是类型安全的,允许对不同的货币求和
sumValue::Value->Value->Value
sumValue=未定义
--这也是不安全的
转换::转换率->价值->货币->价值
convert=未定义
或者我可以为每种货币使用一种类型,但我无法轻松创建和处理它们的汇率

{-#语言GADTSyntax}
{-#语言存在量化}
类型编号=有理数
数据美元=美元
数据欧元=欧元
数据GBP=GBP
a类货币
实例货币美元
实例货币欧元
实例货币GBP
数据值a在哪里
值::货币a=>a->a值
数据转换率a b,其中
转换率::(货币a、货币b)=>数字->转换率a、b
--现在我可以进行类型安全的货币操作
sumValue::货币a=>a值->a值
sumValue=未定义
--我可以确保我的转换是有意义的
转换::转换率AB->值a->b
convert=未定义
--但我不能拥有一个可以轻松操纵的转换率列表
类型转换率=??
我现在是怎么做的 我目前的解决方案是不同类型的货币和货币期权类型之间的同构,希望在程序的不同部分都能做到两全其美。但这是一个混乱的工作

{-#语言存在量化}
类型编号=有理数
数据符号=美元|欧元|英镑
数据美元=美元
数据欧元=欧元
数据磅=磅
a类货币在哪里
toSymbol::a->Symbol
实例货币美元,其中toSymbol=美元
实例货币欧元,其中toSymbol=欧元
实例货币磅,其中toSymbol=英镑
数据包装器=用于所有a。货币a=>包装器a
toCurrency::Symbol->Wrapper
如何在某些函数中具有类型安全性,而在其他函数中具有相同类型值的便利性?。看起来像是
数据种类的工作
,但我看不出有什么帮助


请记住,我在编码时没有所有的数据。它将从API获取

对于任何合理的“最佳”概念,我不能保证这是“最佳”方法,但这里有一个尝试

{-# LANGUAGE GADTs, DataKinds, KindSignatures, ScopedTypeVariables,
  AllowAmbiguousTypes, TypeApplications #-}
{-# OPTIONS -Wall #-}

module Currency where

type Number = Rational
我们首先定义一个
货币
类型和一些相关的辅助机器

data Currency = USD | EUR | GBP
我们添加了一个相关的单例GADT

-- Singleton type for Currency
data SCurrency (cur :: Currency) where
    S_USD :: SCurrency 'USD
    S_EUR :: SCurrency 'EUR
    S_GBP :: SCurrency 'GBP
我们还定义了一个helper类来链接这两种类型(basic和singleton)。我们可以不用,但很方便

-- Helper class
class CCurrency (cur :: Currency) where
    sing :: SCurrency cur
instance CCurrency 'USD where sing = S_USD
instance CCurrency 'EUR where sing = S_EUR
instance CCurrency 'GBP where sing = S_GBP
我们需要对singleton类型使用异构相等运算符

-- Like (==), but working on potentially different types
sameCur :: SCurrency cur1 -> SCurrency cur2 -> Bool
sameCur S_USD S_USD = True
sameCur S_EUR S_EUR = True
sameCur S_GBP S_GBP = True
sameCur _     _     = False
理想情况下,我们应该有
sameCur::SCurrency cur1->SCurrency cur2->(cur1:~:cur2)((cur1:~:cur2)->Void)
,但布尔值就足够了

预赛结束。现在,我们可以使用编译时已知的货币为值定义一个类型

data Value (cur :: Currency) =  Value Number
我们还有一个值类型,其货币只有在运行时才知道

data AnyValue where
    AnyValue :: CCurrency cur => Value cur -> AnyValue
转换率与原始代码相似,只是它们带有单例

data ConversionRate where
    CR :: SCurrency cur1 -> SCurrency cur2 -> Number -> ConversionRate

conversionRates :: [ConversionRate]
conversionRates = [CR S_GBP S_EUR 1.2]
我们现在可以定义类型安全总和

sumValue :: Value cur -> Value cur -> Value cur
sumValue (Value x) (Value y) = Value (x+y)
我们还可以编写两种风格的类型安全转换

convert :: forall newCur. CCurrency newCur =>
           ConversionRate
        -> AnyValue
        -> Maybe (Value newCur)
convert (CR old new rate) (AnyValue (Value val :: Value cur)) =
    if sameCur old (sing @ cur) && sameCur new (sing @ newCur)
    then Just $ Value $ val*rate
    else Nothing

convert' :: forall oldCur newCur. (CCurrency oldCur, CCurrency newCur) =>
            ConversionRate
         -> Value oldCur
         -> Maybe (Value newCur)
convert' cr val = convert cr (AnyValue val)

哇,那真是一段旅程!非常感谢。我会试试的。@MarceloLazaroni我认为上面的初步代码可以使用包自动生成,减轻了工作量,但是知道发生了什么并能够手动编写仍然很有用。