Ralph
Ralph

Reputation: 32264

Introspecting function names in a GHC module

Is there a way in the Glasgow Haskell Compiler to introspect the names of all functions in a module?

I am trying to create an automatic database migration system that, given the names of migration modules, introspects the names of the functions inside and calls them one at a time.

Something like

doMigrations("Migrations.M_2015")
doMigrations("Migrations.M_2016")
-- ...

where Migration.M_2015 contains

module Migration.M_2015
where

migration_2015_01_02 :: DbConnection -> Status
migration_2015_01_02 connection =
    -- ...

Each doMigration will reflect the names of the migration functions in its module and only call those that have not been run before (names saved in a DB table). This will only be called at application start-up, so performance is not a big issue. The reflection can occur at either compile-time or run-time.

Upvotes: 2

Views: 164

Answers (1)

Robert M. Lefkowitz
Robert M. Lefkowitz

Reputation: 1495

In order to do this, you need to use the GHC API -- which is included in the ghc package (which is hidden) -- and is poorly documented.

I attach here a simple program which will print out the list of top level items exported in a module. This should serve as a starting point. This is a little command line utility which takes two arguments -- a module name and the word "class", "data", "function". So, for example:

test Prelude function

will print a list of functions exported by the module (those that are not constructors or defined in a class).

In order to compile this (assuming it is in test.hs) you will need to do:

ghc -package ghc test

in order to make the GHC API packages available.

Here's the code:

import Data.List ( (\\) )
import Data.Maybe (fromJust, catMaybes)
import System.Environment (getArgs)

-- the GHC API stuff

import GHC
import GHC.Paths (libdir)
import ConLike ( ConLike(..) )
import Outputable (showPpr, showSDocUnqual)
import Var (tyVarName)

showU dfs = showSDocUnqual dfs . pprParenSymName

main = do
   (mn : ty : _) <- getArgs
   a <- runGhc (Just libdir) $ do 
     dflags <- getSessionDynFlags
     setSessionDynFlags dflags
     mm <- lookupModule (mkModuleName mn) Nothing
     mi <- fmap fromJust $ getModuleInfo mm
     res <- fmap catMaybes $ mapM lookupName (modInfoExports mi)
     return $ case ty of
       "class" -> [showU dflags c' | c@(ATyCon c') <- res, isClassTyCon c']
       "data" -> [showU dflags c' | c@(ATyCon c') <- res, (not . isClassTyCon) c']
       "function" -> let cf = map getName $ concat [(classMethods . fromJust . tyConClass_maybe) c' | c@(ATyCon c') <- res, isClassTyCon c']
                         df = map getName $ concat [ tyConDataCons c' | c@(ATyCon c') <- res, (not . isClassTyCon) c']
                         ff = [ getName c | c@(AnId{}) <- res] \\ cf
                         fd = [ getName c | c@(AConLike (RealDataCon{})) <- res] \\ df
                     in [showU dflags x | x <- ff ++ fd]
       _ -> ["need to specify: class, data, or function"]
  print a

The list of classes and defined data are pretty straightforward. The list of defined functions includes functions defined in classes and constructors. The above code, for functions, excludes these ( with \\ cf and \\ df ).

a is the generated list of function (or class or data) names.

The code which would invoke these functions would be the subject of a different question (and answer).

lookupModule is the function which loads the module for analysis.

The combination of getModuleInfo and modInfoExports get the list of "stuff" which includes the list of functions exported from the module.

The rest of the code is about getting those names in a usable form.

Upvotes: 1

Related Questions