Data.Typeable.cast 到存在类型

Data.Typeable.cast to an existential type

所以这是我的对象系统传奇的延续 (, )。

这部分基本上归结为以下内容。

{-# LANGUAGE ExistentialQuantification #-}
{-# LANGUAGE ConstraintKinds #-}
{-# LANGUAGE KindSignatures #-}

import Data.Typeable
import GHC.Exts (Constraint)

data Obj (cls :: * -> Constraint) = forall o. (cls o, Typeable o) => Obj o

downcast :: Obj a -> Maybe (Obj b)
downcast (Obj x) = do
                     cx <- cast x
                     return $ Obj cx

downcast 的定义失败并出现此错误:

• Could not deduce: b o0 arising from a use of ‘Obj’
  from the context: (a o, Typeable o)
    bound by a pattern with constructor:
               Obj :: forall (cls :: * -> Constraint) o.
                      (cls o, Typeable o) =>
                      o -> Obj cls,
             in an equation for ‘downcast’
    at downcast.hs:12:11-15
• In the second argument of ‘($)’, namely ‘Obj cx’
  In a stmt of a 'do' block: return $ Obj cx
  In the expression:
    do cx <- cast x
       return $ Obj cx
• Relevant bindings include
    cx :: o0 (bound at moo.hs:13:22)
    downcast :: Obj a -> Maybe (Obj b) (bound at downcast.hs:12:1)

我不太明白为什么会出现这个错误:( 可以修复吗?

当您的 Haskell 被翻译成 GHC Core 时,类(以及随之而来的逻辑编程结构,如蕴涵)已经无处可寻。它们被编译器转换为 字典传递 代码——每个 instance 成为一个记录(一个常规值)并且每个方法成为该记录的一个成员。 (更多细节见。)

所以一个打包约束的构造函数,

data Obj c where  -- I'm using GADT syntax
    Obj :: c a => a -> Obj c

确实在运行时间由常规产品类型表示,

data Obj c where
    Obj :: c a -> a -> Obj c

其中 c a 字段是表示 c a 实例的 运行time 方法字典。

在 运行 时将 Obj c 向下转换为 Obj c',即使您有办法测试具体的 a 是两者的实例 cc',你仍然需要以某种方式为 c' 合成一个字典。由于 c' 通常会包含比 c 更多的方法,这相当于要求计算机为您编写程序。


, I think your best bet would be to build knowledge about your specific class hierarchy into your runtime system under a closed-world assumption. If you have an oracle which can look up an instance's actual runtime dictionary,

oracle :: MonadRuntime m => TypeRep a -> TypeRep c -> m (Maybe (Dict (c a)))

然后你可以写cast(有一些不舒服的table类型争论):

data Obj c where
    Obj :: c a => TypeRep a -> a -> Obj c

cast :: forall c c' m. (MonadRuntime m, Typeable c') => Obj c -> m (Maybe (Obj c'))
cast (Obj tr x) = do
    mdict <- oracle tr (typeRep @c')
    case mdict of
        Just Dict -> return (Just (Obj tr x))
        Nothing -> return Nothing

请注意,此 cast 实际上允许您(尝试)将对象的接口更改为 任何 其他接口,而不仅仅是那些从对象的静态类型派生的接口. (在 C# 中,您可以通过向上转换为 object 然后向下转换来实现。)您可以通过在 cast 的上下文中要求一个 entailment 来防止这种情况:

cast :: forall c c' m. (MonadRuntime m, Typeable c', Class c c') => Obj c -> m (Maybe (Obj c'))

(当然,在 运行 时实际上不会使用该蕴含。)


挑战在于实施oracle!我认为这将是 不好玩 的挑战之一,所以我会给你一两个提示。

你的 Runtime monad 可能是某种 Reader 带有查找 table 映射(TypeRepacs 到字典。 as 和 cs 将需要存在量化,以便将它们存储在异构列表中。

data TableEntry where
    TableEntry :: c a => TypeRep c -> TypeRep a -> TableEntry

type MonadRuntime = MonadReader [TableEntry]

然后oracle需要查找匹配class/type对的TableEntry,然后打开existential,拆开typeRep建立类型相等和 return Just Dict。 (特别是这部分听起来像是编写代码的噩梦。)

在你 运行 你的 MonadRuntime 程序之前,你需要构建 Table 包含你的程序关心的所有实例。

table = [
    TableEntry (typeRep @Ord) (typeRep @Int),
    TableEntry (typeRep @Eq) (typeRep @Bool)
    ]

总而言之,我不明白这有什么值得头疼的。类型 类 从根本上不同于 OO 类(它们甚至不像 OO 接口),因此很难使用它们来建模 OO 类 就不足为奇了。