有没有办法限制可能的产品类型的实例?

时间:2017-02-16 09:49:14

标签: haskell types

让我们假设这两种总和类型

data Currency =
    | GBP
    | EUR
    | DKK

data Country =
    | DE
    | AT
    | DK
    | UK

以及以下产品类型

 type CC = (Country, Currency)

现在即使所有这些国家都是欧盟的一部分(是的,亲爱的3000年软件考古学家 - 英国曾经是欧盟的一部分;-))他们有不同的货币(或不是)。所以我想将CC的可能值限制为

 (DE, EUR)
 (AT, EUR)
 (UK, GBP)
 (DK, DKK)

并使其他所有组合都不可表达 是否可以在类型级别上表达这样的东西?
如果不是那么精通Haskeller的方法怎么会这样呢?

3 个答案:

答案 0 :(得分:8)

这可能有点过分,但根据你所处的背景,你可以使用GADT。这至少取决于您的至少您的货币没有任何构造函数信息。

{-# LANGUAGE GADTs, DataKinds #-}

data Currency = GBP | EUR | DKK

data Country c where
  DE :: Country EUR
  AT :: Country EUR
  UK :: Country GBP
  DK :: Country DKK

或者,我认为一个变体可能不太有用但更接近问题

{-# LANGUAGE GADTs, DataKinds #-}

data Currency = GBP | EUR | DKK
data Country = DE | AT | DK | UK

data CountryCurrency country currency where
  DECC :: CountryCurrency DE EUR
  ATCC :: CountryCurrency AT EUR
  UKCC :: CountryCurrency UK GBP
  DKCC :: CountryCurrency DK DKK

没有用例,很难说最好的方法是什么。 :)

答案 1 :(得分:4)

我的论点是产品类型是表示此映射的错误方式,并且这些类型不是检查其一致性的好工具。 (如何确保您在类型中获得了正确的映射?如果映射发生了变化,那么您也需要更改类型。)

每个国家/地区只有一种货币,货币由国家/地区唯一确定。听起来更像是一个功能,而不是一对。

currency :: Country -> Currency
currency DE = EUR
currency AT = EUR
currency UK = GBP
currency DK = DKK

答案 2 :(得分:1)

[编辑:我忽略了OP在类型级别上要求解决方案的事实,用于编译时检查;我的答案没有回答这个问题,但在类似情况下通常可以作为一个很好的选择]

我认为这样做的惯用方法是使用smart constructors。简而言之:您在模块中定义数据类型,而不导出“危险”数据构造函数(允许“非法”组合的构造函数);而是导出一个只生成合法值的函数。在你的情况下:

module Money ( CountryCurrency
             , Country (..)
             , Currency (..)
             , cc
             ) where

data Currency = GBP | EUR | DKK deriving Show

data Country = DE | AT | DK | UK deriving Show

data CountryCurrency = CC Country Currency deriving Show

cc :: Country -> Currency -> CountryCurrency
cc DE EUR = CC DE EUR
cc AT EUR = CC AT EUR
cc UK GBP = CC UK GBP
cc DK DKK = CC DK DKK
cc _ _ = error "invalid country/currency combination"

cc是智能构造函数。然后,您可以制作如下有效组合:

*Main> cc DE EUR
CC DE EUR

*Main> cc UK GBP
CC UK GBP

但你不能制造非法的:

*Main> cc UK EUR
*** Exception: invalid country/currency combination
CallStack (from HasCallStack):
  error, called at ./Money.hs:18:10 in main:Money

最重要的是,您不能直接使用数据构造函数CC

*Main> CC UK EUR

<interactive>:26:1: error:
    Data constructor not in scope: CC :: Country -> Currency -> t