Can we extract argument names from haskell function?
What extracting the argument names means, when we had a function
foo
:
| foo :: Int -> Int -> Int -> String
| foo apple banana cherry = show (apple*60 + banana*30 + cherry*10)
We want to get the bounded variables: 'apple', 'banana', and 'cherry'. It is possible to write this argument extracter with template haskell, generics, and haskell source code representation.
We want to call the extracter function funArgs
by writing something
like below, in caller module:
| fooargs :: [String]
| fooargs = $(funArgs 'foo)
Loading the module in ghci, and viewing the result:
| ghci> print fooargs
| ["apple","banana","cherry"]
We see variable names: apple
, banana
, and cherry
.
> module FunArgs (funArgs) where
>
> import Control.Monad (when)
> import Data.Generics (everything, mkQ)
> import Language.Haskell.TH hiding (Match)
> import System.Directory (doesFileExist)
Using haskell-src-exts
package. Alternatively, one can use
haskell-src
package, when working with haskell98 source codes only.
Importing with hiding Name
and listE
, since it conflicts with
Language.Haskell.TH
.
> import Language.Haskell.Exts hiding (Name, listE)
The worker function will take a Name
of target function to extract
variable names, and returns TH expression.
> funArgs :: Name -> ExpQ
Firstly, accessing the source code of caller. In current implementation,
funArgs
cannot extract a function defined in ghci interactively. In
this case, merely calling error
.
> funArgs fname = do
> path <- loc_filename `fmap` location
> exists <- runIO $ doesFileExist path
> when (not exists) $ error $ "Cannot access: " ++ path
> result <- runIO $ parseFileWithMode defaultParseMode path
> case result of
> ParseFailed l e -> error $ "Failed to parse: " ++ show l ++ ", " ++ e
> ParseOk mdl -> extractArgs mdl fname
When we can acces the source code, parsing the source code to get
haskell representation. Parsing may fail, and in that case, showing
error message with error
again. There should be nicer way to handle
these error cases, though we just print out the messages, and moving on
for now.
Guts of argument extracter is shown below. Applying pattern match and
collecting matches with everything
and mkQ
. When we find the target
function, extracting the contents of Ident
.
> extractArgs :: Module -> Name -> ExpQ
> extractArgs mdl fname = do
> let args = everything (++) ([] `mkQ` arg) mdl
> arg m = case m of
> Match _ (Ident idnt) ps _ _ _
> | idnt == name' -> map unPVar ps
> | otherwise -> []
> _ -> []
> name' = nameBase fname
> unPVar pv = case pv of
> PVar n -> case n of Ident i -> i; _ -> ""
> _ -> ""
> listE (map (litE . stringL) args)
By the way, this argument extracter was heavily inspired by language-haskell-extract.