Haskell 为什么类型系统拒绝我看似有效的程序?
注意这个节目:Haskell 为什么类型系统拒绝我看似有效的程序?,haskell,types,functional-programming,type-conversion,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 conver
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_转换为D b)=>D->b,这似乎是不可能的。是否有任何方法可以实现和编译一个函数,该函数执行my
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
如果您的程序对您来说确实有效,那么您就能够在Haskell而不是handwave中编写完成您想要的任务的get
。让我来帮你改进你的手波,并找出你为什么要把月亮挂在棍子上
我想表达的是:get::(将包含的a转换为D b)=>D->b
,这似乎是不可能的
如上所述,这并不像你需要的那样精确。事实上,这就是哈斯克尔现在给你的,在这方面
get :: (Convert A b, Convert B b, Convert C b) => D -> b
任何可由D
包含的a
都需要一次转换为该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
的两个不同元素,就Haskell类型系统而言,在每种类型中,所有不同的东西都是相同的。如果要区分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
s,而是指您手中的D::D
,约束可以通过检查它的D
来精确计算所需的内容
除了我的pi
,没有什么能让它消失
不幸的是,虽然圆周率从天空迅速下降,但它还没有降落。尽管如此,与月球不同的是,它可以用棍子触及。毫无疑问,您会抱怨我正在更改设置,但实际上我只是将您的程序从大约2017年的Haskell翻译为2015年的Haskell。总有一天,你会用我亲手留下的那种类型把它拿回来
没有什么你可以说,但你可以唱歌
第一步。打开datatypes
和KindSignatures
并为您的类型构建单例(或者让Richard Eisenberg为您完成)
其思想是(i)数据类型变为种类,(ii)单例描述具有运行时表示的类型级数据。因此,如果a
存在,则在运行时存在类型级别DA
,以此类推
第二步。猜猜谁要来参加晚餐
。打开TypeFamilies
type family DInner (d :: D) :: * where
DInner (DA a) = A
DInner (DB b) = B
DInner (DC c) = C
第三步。给你一些RankNTypes
,现在你就可以写了
get :: forall x. forall d. Dey d -> (Convert (DInner d) x) => x
-- ^^^^^^^^^^^^^^^^^^
-- this is a plausible fake of pi (d :: D) ->
第四步。试着写get
,然后把事情搞砸。我们必须匹配运行时证据,证明类型级别d
是可表示的。我们需要它来获得专门计算晚餐的类型级别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
e现在是单例的,为了convert
,我们需要底层数据。我们需要更多的单粒子装置
第五步。引入并实例化singleton类,以“降级”类型级别的值(只要我们知道它们的运行时代表)。同样,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)
第六步。去做吧
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
s将是实际的DA
s,而那些x
s将不再需要sung
。我的get
的handwave类型将是Haskell,您的get
代码也可以。但与此同时
main = do
print (get (DCey Cey) :: B)
print (get (DBey Bey) :: A)
打字检查很好。也就是说,您的程序(加上晚餐和正确的get类型)看起来像是有效的依赖Haskell,我们就快到了。Hmm,谢谢!我比你投了更高的票,因为这信息丰富且正确,但我特别要求解决这个问题。@Viclib我补充了一些内容,但我不确定是否有任何解决方法允许您按原样使用。只要您有一个
Convert C A
实例,即使它是instance Convert C A,其中Convert\u=error“无法从C转换为”
,它的编译效果就很好。是否有一个原因,你不能简单地做到这一点?如果你关心的是类型安全(这是一个部分功能),你可以考虑使它<代码> MayBeNovit::A-也许B < /代码>有一天我希望我
main = do
print (get (DCey Cey) :: B)
print (get (DBey Bey) :: A)