rankntypes:非法多态或限定类型

rankntypes: Illegal polymorphic or qualified type

我正在尝试扩展(或试图找出是否有可能扩展)一个类型签名已经达到我的知识极限的函数,因为我正在使用的库公开了相当多态的类型和 APIs,甚至更多的东西。

我正在使用 persistenthsqml,这两个库有时都有非常复杂的类型,至少对我来说是这样。

我需要将 persistent 个实体包装到 hsqml "proxy" 个对象,以便暴露给 QML(基本上是 javascript)。 为此,我给自己做了一个帮手,叫做getStandardClassMembers。你可以这样使用它:

instance DefaultClass (Entity Project) where
    classMembers = getStandardClassMembers
        [
            ("name", projectName),
            ("hasCustomIcon", text . not . BS.null . projectIcon),
            ("hasDev", projectHasDev), -- TODO bool as string, ugly..
            ("hasUat", projectHasUat),
            ("hasStaging", projectHasStage),
            ("hasProd", projectHasProd)
        ]
        []

getStandardClassMembers的定义是there。即定义一个javascript对象,我指定的成员,调用我给的haskell函数实现JS对象的成员

这很不错,但是有一个 'but'。该对中第二个位置的函数必须 return Text。在这里你看,除了第一个功能,我宁愿 return 一个 Bool。所以理想情况下,我会让 getStandardClassMembers 的类型签名采用更具多态性的类型,而不是要求第二个函数 return 必须是 Text。再一次,我只是不知道这是否可能,但我决定试一试。

所以我将 RankNTypes 添加到该文件的已经庞大的语言扩展列表中(由于我选择的库,我有点被迫这样做,否则这些库非常棒)。

然后我改变:

getStandardClassMembers :: (Marshal tr, ToBackendKey SqlBackend record, Typeable record,
     MarshalMode tr ICanReturnTo () ~ Yes) =>
    [(String, record -> tr)] -> [(String, ObjRef (Entity record) -> Maybe Int)]
    -> [Member (GetObjType (ObjRef (Entity record)))]

收件人:

getStandardClassMembers :: (Marshal tr, ToBackendKey SqlBackend record, Typeable record,
     MarshalMode tr ICanReturnTo () ~ Yes) =>
    [(String, forall tr. record -> tr)] -> [(String, ObjRef (Entity record) -> Maybe Int)]
    -> [Member (GetObjType (ObjRef (Entity record)))]

(所以我加了forall tr.) 我得到:

Illegal polymorphic or qualified type: forall tr. record -> tr
Perhaps you intended to use ImpredicativeTypes

现在我对这些事情的了解已经够多了,我知道启用 ImpredicativeTypes 并不是一个好主意。另外,如果我启用它,它也不起作用。

我想要的是可以实现的还是我应该休息一下并感谢那些已经非常混乱的类型签名和语言扩展集合的任何工作?

编辑 可能正确的解决方法是忘掉我的助手,回到使用 hsqml 基本 API 的基础。但除此之外,我仍然很好奇这是否可能。

我认为基本问题是您试图将不同类型的值放在一个列表中。即使 ImpredicativeTypes 也不允许您以您需要的方式进行操作。存在类型可以与包装器一起使用,但我认为它们也太过分了。

相反,我建议根据您实际使用 元组的方式,从插入元组更改为插入始终相同类型的内容。查看您的链接代码,我看到

\(name, f) -> defPropertyConst name (return . f . entityVal . fromObjRef)

作为您在元组上使用的函数,可以从中获得您实际上需要的东西。那么为什么不为此定义一个全局函数呢?

stdMember name f = defPropertyConst name (return . f . entityVal . fromObjRef)

(名称只是一个建议;您可能需要更短的名称,甚至是运算符。)

然后更改 getStandardClassMembers 以将此类值的列表作为其第一个参数,第二个参数可能类似。

假设没有更多类型的细微差别,那么您应该可以编写

instance DefaultClass (Entity Project) where
    classMembers = getStandardClassMembers
        [
            stdMember "name" projectName,
            stdMember "hasCustomIcon" $ text . not . BS.null . projectIcon,
            stdMember "hasDev" projectHasDev, -- TODO bool as string, ugly..
            stdMember "hasUat" projectHasUat,
            stdMember "hasStaging" projectHasStage,
            stdMember "hasProd" projectHasProd
        ]
        []