为什么类型系统拒绝我看似有效的程序?
Why is the type system refusing my seemingly valid program?
注意这个程序:
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 :: (Convert a_contained_by_D b) => D -> b,貌似不可能。有没有什么方法可以实现和编译一个函数来完成我的 get
尝试做的事情,而不改变其余的设置?
要使该代码正常工作,它必须与任何相同类型的参数一起工作。也就是说,如果
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
如果你的程序真的对你来说似乎有效,那么你就可以在[=118=中编写完成你想要的工作的get
类型],不是手波。让我来帮助您改善您的手波并揭开您要求棍子上的月亮的原因。
What I want to express is: get :: (Convert a_contained_by_D b) => D -> b
, which seems impossible.
如前所述,这并不像您需要的那样精确。确实,这就是 Haskell 现在给你的,
get :: (Convert A b, Convert B b, Convert C b) => D -> b
任何可以被 D
包含的 a
都需要一次一个,才能转换为 b
。这就是为什么你得到 classic 系统管理员逻辑:不允许获得 D
除非他们都可以 b
.
问题是您需要知道的状态不是 any old D
中可能包含的类型,而是特定中包含的类型D
您收到的输入。正确的?你要
print (get (DB B) :: A) -- this to work
print (get (DC C) :: A) -- this to fail
但是 DB B
和 DC C
只是 D
的两个不同元素,就 Haskell 类型系统而言,每个类型 [=111] =]一切不同都是一样的。如果你想区分 D
的元素,那么你需要一个 D
-pendent 类型。这是我用 handwave 写的。
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
正在从天而降,但它还没有落地。 None越少,不像月亮,用棍子就能够到。毫无疑问你会抱怨我正在更改设置,但实际上我只是将你的程序从大约 2017 年的 Haskell 翻译到 2015 年的 Haskell。你会 get
它回来,一个那天,我用手挥过的那种类型。
没有什么可以说的,但是可以唱歌。
第 1 步。打开 DataKinds
和 KindSignatures
并为您的类型构建单例(或让 Richard Eisenberg 为您完成)。
data 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)
想法是 (i) 数据类型成为种类,以及 (ii) 单例表征具有 运行 时间表示的类型级数据。因此,类型级别 DA a
存在于 运行 时间,前提是 a
存在,等等
第 2 步。猜猜谁会来 DInner
。打开 TypeFamilies
.
type family DInner (d :: D) :: * where
DInner (DA a) = A
DInner (DB b) = B
DInner (DC c) = C
第 3 步。给你一些 RankNTypes
,现在你可以写
get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
-- ^^^^^^^^^^^^^^^^^^
-- this is a plausible fake of pi (d :: D) ->
第 4 步。尝试写 get
并搞砸了。我们必须在 运行 时间证据上匹配类型级别 d
是可表示的。我们需要它来获得专门用于计算 DInner
的类型级别 d
。如果我们有适当的 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
es 现在是单例,为了 convert
,我们需要基础数据。我们需要更多的单例设备。
第5步。引入并实例化单例class,以"demote"类型级别值(只要我们知道它们的运行时间代表)。同样,Richard Eisenberg 的 singletons
库可以从中提取模板-Haskell 样板,但让我们看看发生了什么
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)
第 6 步。执行。
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)
请放心,当我们拥有适当的 pi
时,那些 DAey
将是实际的 DA
而那些 x
将不再需要 sung
。我的 get
手波类型是 Haskell,你的 get
代码就可以了。但与此同时
main = do
print (get (DCey Cey) :: B)
print (get (DBey Bey) :: A)
类型检查很好。也就是说,您的程序(加上 DInner
和 get
的正确类型)似乎是有效的 Dependent Haskell,我们快到了。
注意这个程序:
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 :: (Convert a_contained_by_D b) => D -> b,貌似不可能。有没有什么方法可以实现和编译一个函数来完成我的 get
尝试做的事情,而不改变其余的设置?
要使该代码正常工作,它必须与任何相同类型的参数一起工作。也就是说,如果
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
如果你的程序真的对你来说似乎有效,那么你就可以在[=118=中编写完成你想要的工作的get
类型],不是手波。让我来帮助您改善您的手波并揭开您要求棍子上的月亮的原因。
What I want to express is:
get :: (Convert a_contained_by_D b) => D -> b
, which seems impossible.
如前所述,这并不像您需要的那样精确。确实,这就是 Haskell 现在给你的,
get :: (Convert A b, Convert B b, Convert C b) => D -> b
任何可以被 D
包含的 a
都需要一次一个,才能转换为 b
。这就是为什么你得到 classic 系统管理员逻辑:不允许获得 D
除非他们都可以 b
.
问题是您需要知道的状态不是 any old D
中可能包含的类型,而是特定中包含的类型D
您收到的输入。正确的?你要
print (get (DB B) :: A) -- this to work
print (get (DC C) :: A) -- this to fail
但是 DB B
和 DC C
只是 D
的两个不同元素,就 Haskell 类型系统而言,每个类型 [=111] =]一切不同都是一样的。如果你想区分 D
的元素,那么你需要一个 D
-pendent 类型。这是我用 handwave 写的。
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
正在从天而降,但它还没有落地。 None越少,不像月亮,用棍子就能够到。毫无疑问你会抱怨我正在更改设置,但实际上我只是将你的程序从大约 2017 年的 Haskell 翻译到 2015 年的 Haskell。你会 get
它回来,一个那天,我用手挥过的那种类型。
没有什么可以说的,但是可以唱歌。
第 1 步。打开 DataKinds
和 KindSignatures
并为您的类型构建单例(或让 Richard Eisenberg 为您完成)。
data 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)
想法是 (i) 数据类型成为种类,以及 (ii) 单例表征具有 运行 时间表示的类型级数据。因此,类型级别 DA a
存在于 运行 时间,前提是 a
存在,等等
第 2 步。猜猜谁会来 DInner
。打开 TypeFamilies
.
type family DInner (d :: D) :: * where
DInner (DA a) = A
DInner (DB b) = B
DInner (DC c) = C
第 3 步。给你一些 RankNTypes
,现在你可以写
get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
-- ^^^^^^^^^^^^^^^^^^
-- this is a plausible fake of pi (d :: D) ->
第 4 步。尝试写 get
并搞砸了。我们必须在 运行 时间证据上匹配类型级别 d
是可表示的。我们需要它来获得专门用于计算 DInner
的类型级别 d
。如果我们有适当的 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
es 现在是单例,为了 convert
,我们需要基础数据。我们需要更多的单例设备。
第5步。引入并实例化单例class,以"demote"类型级别值(只要我们知道它们的运行时间代表)。同样,Richard Eisenberg 的 singletons
库可以从中提取模板-Haskell 样板,但让我们看看发生了什么
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)
第 6 步。执行。
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)
请放心,当我们拥有适当的 pi
时,那些 DAey
将是实际的 DA
而那些 x
将不再需要 sung
。我的 get
手波类型是 Haskell,你的 get
代码就可以了。但与此同时
main = do
print (get (DCey Cey) :: B)
print (get (DBey Bey) :: A)
类型检查很好。也就是说,您的程序(加上 DInner
和 get
的正确类型)似乎是有效的 Dependent Haskell,我们快到了。