Emmanuel Touzery
Emmanuel Touzery

Reputation: 9173

rankntypes: Illegal polymorphic or qualified type

I am trying to expand (or trying to find out whether it's possible to expand) a function with a type signature that already goes to the limits of my knowledge, because of the libraries I'm using which expose quite polymorphic types and APIs, to something even more polymorphic.

I'm using persistent and hsqml, both libraries having pretty hairy types at moments, at least for me.

I need to wrap persistent entities to hsqml "proxy" objects to be exposed to QML (javascript basically). For that purpose I made myself a helper, called getStandardClassMembers. You use it like so:

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)
        ]
        []

The definition of getStandardClassMembers is there. That define a javascript object with the members I specify, and calls the haskell functions I give to implement the members of the JS object.

That's pretty nice but there is a 'but'. The function in the second position in the pair must return Text. Here you see, for all but the first function, I should rather return a Bool. So ideally I would make the type signature of getStandardClassMembers take a type more polymorphic to not require that that second function returns necessarily a Text. And again I simply don't know if it's possible, but I decided to give it a shot.

So I add RankNTypes to the already massive list of language extensions for that file (I'm kind of forced into that due to the libraries I chose, which are otherwise wonderful).

And I change:

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)))]

To:

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)))]

(so I added forall tr.) and I get:

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

Now I know just enough about these things to know that enabling ImpredicativeTypes is not a good idea. Plus if I enable it, it doesn't work either.

Is what I want achievable or should I give it a rest and be thankful anything works at all with those already pretty messy type signatures and language extension collections?

EDIT probably the right fix is to forget about my helper and go back to the basics using the hsqml basic API. But otherwise I'm still curious whether that would be possible or not.

Upvotes: 2

Views: 268

Answers (1)

Ørjan Johansen
Ørjan Johansen

Reputation: 18189

I think the basic problem is that you are trying to put values of different types in one list. Even ImpredicativeTypes doesn't really allow you to do that in the way that you would need. Existential types could work with a wrapper, but I think they're overkill as well.

Instead, I suggest changing from inserting tuples to inserting something that always is the same type, based on how you were actually going to use the tuples. Looking at your linked code I see

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

as the function you use on your tuples to get what you actually need from them. So why not define a global function for that?

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

(The name is just a suggestion; you might want something shorter or even an operator.)

Then change getStandardClassMembers to take a list of such values as its first argument, and probably something similar for the second.

Assuming there are no more type subtleties, you should then be able to write

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
        ]
        []

Upvotes: 1

Related Questions