Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Dynamically get the list of data constructors of a type

I have a data type like this:

data ABCS = A Int | B Int | ... | Z Int deriving (Data, Typeable)

In a test, I want to dynamically extract all the constructors, make an instance from each constructor, then run the test.

I have been looking through Data.Typeable and Data.Data, but I have not yet seen/understood exactly how to do this starting with only the type (ABC).

Help is much appreciated.

like image 797
GreenSaguaro Avatar asked Mar 11 '23 03:03

GreenSaguaro


1 Answers

If you're OK with using Data.Data, it works for this use case, but is a little clunky because of the Int parameters.

{-# LANGUAGE ScopedTypeVariables #-}
import Data.Data
import Data.Typeable

allCtors :: forall a. Data a => [Int -> a]
allCtors = map observeCtor $ dataTypeConstrs $ dataTypeOf (undefined :: a)
  where
    observeCtor :: Constr -> Int -> a
    observeCtor c i = fromJust $ fromConstrM (cast i) c

Then we have e.g.

λ data ABC = A Int | B Int | C Int deriving (Show, Data, Typeable)
data ABC = A Int | B Int | C Int
λ map ($ 2) allCtors :: [ABC]
[A 2,B 2,C 2]

If you don't want to use Data.Data, you might to be able to do this with GHC.Generics and -XDefaultSignatures


FWIW, you wouldn't have to deal with any of this if you could refactor ABC so that the A,B,C tags were their own type...

data ABCTagged = ABCTagged ABC Int deriving Show

data ABC = A | B | C deriving (Show, Eq, Ord, Enum. Bounded)

... then just use enumFrom minBound :: [ABC] to get the whole list. Easy! Not sure how feasible this is for you though.

like image 83
jgriego Avatar answered Mar 20 '23 03:03

jgriego