我可以使用DataKinds编写一个函数来返回由参数编码的类型的值吗?

时间:2015-02-02 18:11:56

标签: haskell types data-kinds

假设我有一种货币类型:

data Currency = USD | EUR | YEN

和存储int的Money类型,并由给定的Currency参数化(Currency被提升为具有DataKinds扩展名的种类)。

data Money :: Currency -> * where
    Money :: Int -> Money c

是否可以编写一个函数moneyOf,该函数将其作为参数,一个货币值,并返回一个Money值,该值由相应类型的Currency值参数化?例如moneyOf :: Currency -> Money c,但我们得到编译时保证c是从Currency值生成的类型?

2 个答案:

答案 0 :(得分:7)

不,但有一些解决方法。如您所见,您需要编写的类型类似于moneyOf :: (c :: Currency) -> Int -> Money c,其中c在函数实现本身的类型中都绑定了{{1} }})。这不是我们在Haskell中可以做的事情。那么我们可以做些什么呢?有两种选择,取决于你真正想要多少。

选项1:代理。定义多边形类型

moneyOf _ amt = Money amt

这种类型背后的想法是,您可以使用data Proxy (t :: k) = Proxy 作为传递类型t的具体术语级表示的方法。因此,例如,我们可以定义:

Proxy :: Proxy t

然后,我们可以将其称为moneyOf :: Proxy c -> Int -> Money c moneyOf _ = Money 来获取moneyOf (Proxy :: Proxy USD) 10。你可以使用的一个技巧是改为给函数类型Money 10 :: Money USD(注意小写proxy k -> Int -> Money c!),这样proxy将与任意函数类型统一.¹这非常适合将参数传递给函数以修复它们的返回类型,但它实际上并没有让你做任何事情。

正如您所描述的那样,我认为代理可能最适合解决它。(假设普通类型签名,如proxy,不起作用,是 - 当你可以使用它们时更简单!)

选项2:单例类型。但是,如果您发现需要更多通用性(或者您只是好奇),那么另一种方法是创建单例类型喜欢以下内容:

Money 10 :: Money USD

这称为“单例类型”,因为每个data SingCurrency (c :: Currency) where SUSD :: SingCurrency USD SEUR :: SingCurrency EUR SYEN :: SingCurrency YEN 只有一个成员(例如SingCurrency cSUSD类型的唯一值)。现在,你可以写

SingCurrency USD

此处,moneyOf :: SingCurrency c -> Int -> Money c moneyOf _ = Money 评估为moneyOf SUSD 10。但仅凭这一点并没有给你带来任何超越使用的东西(除了少一点打字)。当你想要制作单身人士时,单身人士会变得特别有趣:

Money 10 :: Money USD

现在,如果您有class SingCurrencyI (c :: Currency) where sing :: SingCurrency c instance SingCurrencyI USD where scur = SUSD instance SingCurrencyI EUR where scur = SEUR instance SingCurrencyI YEN where scur = SYEN 约束,则可以使用SingCurrencyI c自动生成相应的SingCurrency c值,从而允许您从类型移动水平到学期水平。 (请注意,虽然所有sing都是Currency的实例,但是如果需要,则需要明确指定约束.²)我想不出任何使用它的好例子我的头;我认为我的建议是只有当你发现自己处于一种你无法完成所需要的情况时才会使用单身人士,并意识到单身人士的额外类型 - 价值同步会对你有帮助 (并且你不能在这种情况下重新设计自己)。

如果你确实发现自己使用单身人士,那么机器就会为你in the singletons package设置,更为一般:a data family Sing :: k -> *代替SingCurrencyI;并且a type class SingI :: k -> Constraint取代SingCurrency,其中包含SingCurrencyI个成员。还有sing :: SingI a => Sing a允许您自由地从Sing n转换为SingI n(另一个方向只是sing)。 (这些都在a function withSingI :: Sing n -> (SingI n => r) -> r中提供。)Data.Singletons中还有一些允许你编写的模板Haskell

singletons [d|data Currency = USD | EUR | YEN|]

位于程序的顶层,以便定义Currency类型以及相应的SingSingI个实例。 (您还需要启用以下语言扩展程序:KindSignaturesDataKindsTypeFamiliesGADTsExistentialQuantificationScopedTypeVariables和{{ 1}}。)

这真的很强大 - 它几乎像依赖类型,如果你眯眼 - 但它可能是一个巨大的痛苦使用。事实上,如果你想了解更多信息,那么有一篇论文正是在谈论这个问题:Data.Singletons.TH,Sam Lindley和"Hasochism: The Pleasure and Pain of Dependently Typed Haskell Programming"。对于那些已经考虑过这些想法的人来说,这绝对是可读的,尽管这些材料本身就很棘手;但请注意,他们的符号略有不同。不幸的是,我不知道任何好的博客文章或教程式的介绍。


¹我不确定类型系列的类型统一规则的状态,尽管......。

²否则,将不会传入包含TemplateHaskell的运行时字典,因此该值在运行时将不可用。

答案 1 :(得分:1)

Antal S-Z选项2的替代方案如下:

您保留Currency单身人士SingCurrency

data SingCurrency (c :: Currency) where
    SEUR :: SingCurrency EUR
    SUSD :: SingCurrency USD
    SYEN :: SingCurrency YEN

但是,您可以使用存在的GADT,而不是使用单例实例类(SingCurrencyI)。

data AnyCurrency where
    AnyCurrency :: SingCurrency sc -> AnyCurrency

使用辅助函数代替SingCurrencyI实例。

anyCurrency :: Currency -> AnyCurrency
anyCurrency EUR = AnyCurrency SEUR
anyCurrency USD = AnyCurrency SUSD
anyCurrency YEN = AnyCurrency SYEN

使用

money :: SingCurrency c -> Int -> Money c
money = const Money

和存在主义Money

data AnyMoney where
    AnyMoney :: Money c -> AnyMoney

你可以实施

moneyOf :: Currency -> Int -> AnyMoney
moneyOf c v = case anyCurrency c of
                AnyCurrency sc -> AnyMoney $ money sc v

AnyMoney上的模式匹配将允许您使用带有Money c类型参数的函数,即

useMoney :: Money c -> IO ()
useMoney = undefined

最终会得到你

useUseMoney :: Currency -> Int -> IO ()
useUseMoney c v = case moneyOf c v of
                    AnyMoney m -> useMoney m