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)