介意这个程序:
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
尝试做的事情,而不更改其余的设置?
答案 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 C
和D
只是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.启用KindSignatures
和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)
并为您的类型构建单身人士(或让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
时,DA
个x
将是实际的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