为什么类型系统拒绝我看似有效的程序?

时间:2015-02-25 08:02:34

标签: haskell types functional-programming type-conversion

介意这个程序:

class Convert a b where
    convert :: a -> b

data A = A deriving Show
data B = B deriving Show
data C = C deriving Show
data D = DA A | DB B | DC C deriving Show

instance Convert A A where convert A = A
instance Convert A B where convert A = B
instance Convert A C where convert A = C
instance Convert B A where convert B = A
instance Convert B B where convert B = B
instance Convert B C where convert B = C
-- There is no way to convert from C to A
instance Convert C B where convert C = B
instance Convert C C where convert C = C

get (DA x) = convert x
get (DB x) = convert x
get (DC x) = convert x

main = do
    print (get (DC C) :: B) -- Up to this line, code compiles fine.
    print (get (DB B) :: A) -- Add this line and it doesn't, regardless of having a way to convert from B to A!

有些情况可以从C转换为B,也可以从B转换为A。然而,GHC强调前者,但后者失败了。经过检查,似乎无法推断出get

的足够通用类型
get :: (Convert A b, Convert B b, Convert C b) => D -> b

我想表达的是: get ::(转换a_contained_by_D b)=> D - > b ,这似乎不可能。有没有办法实现和编译一个函数来执行我的get尝试做的事情,而不更改其余的设置?

2 个答案:

答案 0 :(得分:71)

如果您的程序确实对您有效,那么您将能够编写在Haskell中执行您想要的工作的get类型,而不是手动波。让我帮你改善你的手波,并揭示你在棍子上要求登月的原因。

  

我想表达的是:get :: (Convert a_contained_by_D b) => D -> b,这似乎是不可能的。

如上所述,这并不像您需要的那么精确。实际上,这就是Haskell现在给你的东西,

get :: (Convert A b, Convert B b, Convert C b) => D -> b

a可以包含的任何D都是必需的,一次一个,可以转换为b。这就是您获得经典系统管理员逻辑的原因:除非D全部都可以b,否则不允许D

问题是您需要知道任何D中可能包含的类型的状态,而不是特定print (get (DB B) :: A) -- this to work print (get (DC C) :: A) -- this to fail 中包含的类型你收到的作为输入。对?你想要

DB B

但是DC CD只是D的两个不同元素,就Haskell类型系统而言,在每种类型中,所有不同的都是相同的< / strong>即可。如果您想区分D的元素,那么您需要DInner :: D -> * DInner (DA a) = A DInner (DB b) = B DInner (DC c) = C get :: forall x. pi (d :: D) -> (Convert (DInner d) x) => x get (DA x) = convert x get (DB x) = convert x get (DC x) = convert x - 下垂类型。这是我如何用手写的。

pi

其中forall是在运行时传递的数据的绑定表单(与->不同),但可能依赖于哪些类型(与D不同)。现在,约束不是关于任意d :: D,而是关注你手中的DInner,约束可以通过检查pi来精确计算所需的内容。

除了pi之外,没有什么可以说会让它消失。

可悲的是,虽然get正在迅速从天而降,但尚未降落。然而,与月亮不同,它可以用棍子到达。毫无疑问,你会抱怨我正在改变设置,但实际上我只是将你的程序从大约2017年的Haskell翻译到2015年的Haskell。你有一天DataKinds它回来了,有一天,这种类型我手动了。

你无话可说,但你可以唱歌

步骤1.启用KindSignaturesdata A = A deriving Show data Aey :: A -> * where -- think of "-ey" as an adjectival suffix Aey :: Aey 'A -- as in "tomatoey" data B = B deriving Show data Bey :: B -> * where Bey :: Bey 'B data C = C deriving Show data Cey :: C -> * where Cey :: Cey 'C data D = DA A | DB B | DC C deriving Show data Dey :: D -> * where DAey :: Aey a -> Dey (DA a) DBey :: Bey b -> Dey (DB b) DCey :: Cey c -> Dey (DC c) 并为您的类型构建单身人士(或让Richard Eisenberg为您执行此操作)。

DA a

这个想法是(i)数据类型变为种类,以及(ii)单例表征具有运行时表示的类型级数据。因此,在a提供的运行时,类型级DInner存在等等。

第2步。猜猜谁来TypeFamilies。启用type family DInner (d :: D) :: * where DInner (DA a) = A DInner (DB b) = B DInner (DC c) = C

RankNTypes

第3步。给你一些get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x -- ^^^^^^^^^^^^^^^^^^ -- this is a plausible fake of pi (d :: D) -> ,现在你可以写

get

步骤4.尝试编写d并搞砸了。我们必须在运行时匹配类型级d可表示的证据。我们需要这样才能获得专门用于计算DInner的类型级pi。如果我们有适当的D,我们可以匹配Dey d值,该值可以提供双重任务,但就目前而言,请匹配get (DAey x) = convert x -- have x :: Aey a, need x :: A get (DBey x) = convert x -- and so on get (DCey x) = convert x -- and so forth

x

疯狂地,我们的convert es现在是单身人士,在singletons,我们需要基础数据。我们需要更多的单件装置。

步骤5.引入并实例化单例类,以降级&#34;降级&#34;类型级别值(只要我们知道他们的运行时代表)。同样,Richard Eisenberg的class Sing (s :: k -> *) where -- s is the singleton family for some k type Sung s :: * -- Sung s is the type-level version of k sung :: s x -> Sung s -- sung is the demotion function instance Sing Aey where type Sung Aey = A sung Aey = A instance Sing Bey where type Sung Bey = B sung Bey = B instance Sing Cey where type Sung Cey = C sung Cey = C instance Sing Dey where type Sung Dey = D sung (DAey aey) = DA (sung aey) sung (DBey bey) = DB (sung bey) sung (DCey cey) = DC (sung cey) 库可以将Template-Haskell作为样板,但让我们看看发生了什么

get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
get (DAey x) = convert (sung x)
get (DBey x) = convert (sung x)
get (DCey x) = convert (sung x)

步骤6.做。

pi

请放心,当我们有DAey时,DAx将是实际的sung,而get将不再需要get }。 main = do print (get (DCey Cey) :: B) print (get (DBey Bey) :: A) 的手动波类型为Haskell,DInner的代码也没问题。但与此同时

get

typechecks就好了。这就是说,你的程序(加上{{1}}和{{1}}的正确类型)似乎是有效的Dependent Haskell,我们几乎就在那里。

答案 1 :(得分:8)

要使该代码起作用,它必须使用相同类型的任何参数。也就是说,如果

get (DB B) :: A
然后

工作

get anyValueOfTypeD :: A

必须工作,包括

get (DC C) :: A

由于缺少从C到A的实例而无法工作。

第一行

get anyValueOfTypeD :: B

有效,因为我们确实拥有将A,B,C转换为B的所有三个实例。

我认为没有任何解决方法可以让您保留D类型。如果你可以改变它,你可以使用例如。

data D a = DA a | DB a | DC a

(请注意它与原版完全不同),甚至是GADT

data D x where
  DA :: A -> D A
  DB :: B -> D B
  DC :: C -> D C