Is there a way how to enumerate all functions in a module using Template Haskell?

420 views Asked by At

While I can use reify to get information about most other syntactic constructs, I couldn't find anything that would give some information about a module.

1

There are 1 answers

0
Nikita Volkov On

Unfortunately Template Haskell currently has no such capabilities. All the solutions involve parsing of the module's source-code. However the location and loc_filename functions of TH make it easy to locate the module with the calling splice.

Here is a solution extracted from the source code of one of my projects:

{-# LANGUAGE LambdaCase, TupleSections #-}
import Language.Haskell.TH
import qualified Data.Attoparsec.Text as AP
import qualified Data.Text.IO as Text
import qualified Data.Text as Text
import qualified Data.Char as Char
import Data.Maybe
import Data.List
import Control.Applicative
import Data.Traversable
import Prelude hiding (mapM)


reifyLocalFunctions :: Q [(Name, Type)]
reifyLocalFunctions =
  listTopLevelFunctionLikeNames >>=
  mapM (\name -> reifyFunction name >>= mapM (return . (name, ))) >>=
  return . catMaybes
  where
    listTopLevelFunctionLikeNames = do 
      loc <- location
      text <- runIO $ Text.readFile $ loc_filename loc
      return $ map (mkName . Text.unpack) $ nub $ parse text
      where
        parse text = 
          either (error . ("Local function name parsing failure: " ++)) id $
          AP.parseOnly parser text
          where
            parser = 
              AP.sepBy (optional topLevelFunctionP <* AP.skipWhile (not . AP.isEndOfLine)) 
                       AP.endOfLine >>=
              return . catMaybes
              where
                topLevelFunctionP = do
                  head <- AP.satisfy Char.isLower
                  tail <- many (AP.satisfy (\c -> Char.isAlphaNum c || c `elem` ['_', '\'']))
                  return $ Text.pack $ head : tail

reifyFunction :: Name -> Q (Maybe Type)
reifyFunction name = do
  tryToReify name >>= \case
    Just (VarI _ t _ _) -> return $ Just $ t
    _ -> return Nothing

tryToReify :: Name -> Q (Maybe Info)
tryToReify n = recover (return Nothing) (fmap Just $ reify n)