Is it possible to recover constraints when using Dynamic/fromDynamic in Haskell?

Viewed 90

I am designing a system which needs to track tables of different types. It would be highly convenient to have one authoritative list of the tables*, and the best way to have this list would - I think - be as a list of Proxies, as at times I do need the type. I suppose HLists are an option, but even after 2 years of using Haskell a fair amount, they look unwieldy, so I was reaching for Dynamic.

  • Actually, it would be best to have 0 lists, and just have a way to query all available instances of a particular type class at run time, but I'm not sure there's a way to do this (last I checked).

My code currently looks like this:

allCowProxies :: [Dynamic]
allCowProxies = [
    toDyn $ Proxy @HoboRecord
  , toDyn $ Proxy @AF.AfiFarmRecord
  , toDyn $ Proxy @DCP.DcPrediction
  , toDyn $ Proxy @DCF.DcFreshFeatures
  , toDyn $ Proxy @DCH.DcHistorical
  , toDyn $ Proxy @SA.ShActivity
  , toDyn $ Proxy @SRE.ShRumAndEating
  , toDyn $ Proxy @SX.SxRecord
  ]

allCowRecs :: [TypeRep]
allCowRecs = catMaybes $ toTypeRep <$> allCowProxies
  where
    toTypeRep d = case fromDynamic d of
      Just x -> Just $ typeRep x
      Nothing -> Nothing

But this yields an error:

    • No instance for (Typeable proxy0)
        arising from a use of ‘fromDynamic’
    • In the expression: fromDynamic d
      In the expression:
        case fromDynamic d of
          Just x -> Just $ typeRep x
          Nothing -> Nothing
      In an equation for ‘toTypeRep’:
          toTypeRep d
            = case fromDynamic d of
                Just x -> Just $ typeRep x
                Nothing -> Nothing
   |
37 |     toTypeRep d = case fromDynamic d of
   |                        ^^^^^^^^^^^^^

The data types should all implement Typeable, but if they don't, I would expect to just get Nothing here, instead of the compile-time error. I guess, from looking at it, that it isn't fully inferring the type and only the outer type (Proxy).

Are there any workarounds to this, or suggested alternative approaches as well would be welcome.

Update

While @Li-Yao Xia's answer did directly answer the question, I ended up going for an alternative solution and wanted to document that here. It is a bit more conventional but it does require RankNTypes enabled.

data CowRecTbl =
    Hobo   (Proxy HoboRecord)
  | Afi    (Proxy AF.AfiFarmRecord)
  | DcPred (Proxy DCP.DcPrediction)
  | DcFf   (Proxy DCF.DcFreshFeatures)
  | DcHist (Proxy DCH.DcHistorical)
  | ShAct  (Proxy SA.ShActivity)
  | ShRum  (Proxy SRE.ShRumAndEating)
  | SxTec  (Proxy SX.SxRecord)

allCowProxies :: [CowRecTbl]
allCowProxies = [
    Hobo   $ Proxy @HoboRecord
  , Afi    $ Proxy @AF.AfiFarmRecord
  , DcPred $ Proxy @DCP.DcPrediction
  , DcFf   $ Proxy @DCF.DcFreshFeatures
  , DcHist $ Proxy @DCH.DcHistorical
  , ShAct  $ Proxy @SA.ShActivity
  , ShRum  $ Proxy @SRE.ShRumAndEating
  , SxTec  $ Proxy @SX.SxRecord
  ]

cowRecTblApplyPx :: forall a.
  (forall r k. EntityRec r k => Proxy r -> a) -> CowRecTbl -> a
cowRecTblApplyPx f (Hobo px) = f px
cowRecTblApplyPx f (Afi px) = f px
cowRecTblApplyPx f (DcPred px) = f px
cowRecTblApplyPx f (DcFf px) = f px
cowRecTblApplyPx f (DcHist px) = f px
cowRecTblApplyPx f (ShAct px) = f px
cowRecTblApplyPx f (ShRum px) = f px
cowRecTblApplyPx f (SxTec px) = f px


allCowRecs :: [TypeRep]
allCowRecs = (cowRecTblApplyPx typeRep) <$> allCowProxies

With -Werror=incomplete-patterns enabled, all I have to do is remember to add new entries to allCowProxies and the type system should remind me if I forgot anything else. I may still try Carl's suggestion in the comments at some point.

1 Answers

To get a TypeRep (unindexed) from a Dynamic, there's dynTypeRep

dynTypeRep :: Dynamic -> TypeRep   -- TypeRep = SomeTypeRep   in the docs
allCowRecs :: [TypeRep]
allCowRecs = dynTypeRep <$> allCowProxies

I don't get why you need both allCowRecs and allCowProxies, they both encode the same information.

Related