Reputation: 6999
I have a reflective situation where I want to display the types of inputs/outputs of a function. I could just add it to a separate data structure, but then I have duplication and would have to make sure they stay in sync manually.
For example, a function:
myFunc :: (String, MyType, Double) -> (Int, SomeType TypeP, MyOtherType a)
and so now I want to have something like (can be somewhat flexible, esp. when params involved):
input = ["String", "MyType", "Double"]
output = ["Int", "SomeType TypeP", "MyOtherType a"]
defined automatically. It doesn't have to be Strings directly. Is there a simple way to do this?
Upvotes: 3
Views: 1159
Reputation: 2697
You don't really need a custom parser. You can just reflect on the TypeRep value you receive. For instance the following would work:
module ModuleReflect where
import Data.Typeable
import Control.Arrow
foo :: (Int, Bool, String) -> String -> String
foo = undefined
-- | We first want to in the case that no result is returned from splitTyConApp
-- to just return the input type, this. If we find an arrow constructor (->)
-- we want to get the start of the list and then recurse on the tail if any.
-- if we get anything else, like [] Char then we want to return the original type
-- [Char]
values :: Typeable a => a -> [TypeRep]
values x = split (typeOf x)
where split t = case first tyConString (splitTyConApp t) of
(_ , []) -> [t]
("->", [x]) -> [x]
("->", x) -> let current = init x
next = last x
in current ++ split next
(_ , _) -> [t]
inputs :: Typeable a => a -> [TypeRep]
inputs x = case values x of
(x:xs) | not (null xs) -> x : init xs
_ -> []
output :: Typeable a => a -> TypeRep
output x = last $ values x
and a sample session
Ok, modules loaded: ModuleReflect.
*ModuleReflect Data.Function> values foo
[(Int,Bool,[Char]),[Char],[Char]]
*ModuleReflect Data.Function> output foo
[Char]
*ModuleReflect Data.Function> inputs foo
[(Int,Bool,[Char]),[Char]]
This is just some quick barely tested code, I'm sure it can be cleaned up. And the reason I don't use typeRepArgs is that in the case of other constructors they would be broken up to, e.g [] Char returns Char instead of [Char].
This version does not treat elements of tuples as seperate results, but it can be easily changed to add that.
However as mentioned before this has a limitation, It'll only work on monomorphic types. If you want to be able to find this for any type, then you should probably use the GHC api. You can load the module, ask it to Typecheck and then inspect the Typecheck AST for the function and thus find it's type. Or you can use Hint to ask for the type of a function. and parse that.
Upvotes: 7
Reputation: 92966
Have a look at Data.Typeable
: It defined a functiontypeOf :: Typeable a => a -> TypeRep
, TypeRep
is instance of Show
. For example:
$ ghci
GHCi, version 6.12.1: http://www.haskell.org/ghc/ :? for help
:m Loading package ghc-prim ... linking ... done.
Loading package integer-gmp ... linking ... done.
Loading package base ... linking ... done.
Prelude> :m +Data.Typeable
Prelude Data.Typeable> :i TypeRep
data TypeRep
= Data.Typeable.TypeRep !Data.Typeable.Key TyCon [TypeRep]
-- Defined in Data.Typeable
instance [overlap ok] Eq TypeRep -- Defined in Data.Typeable
instance [overlap ok] Show TypeRep -- Defined in Data.Typeable
instance [overlap ok] Typeable TypeRep -- Defined in Data.Typeable
Prelude Data.Typeable> typeOf (+)
Integer -> Integer -> Integer
Prelude Data.Typeable> typeOf (\(a,(_:x),1) -> (a :: (),x :: [()]))
((),[()],Integer) -> ((),[()])
Now, you only need a custom parser that translated this output into something suitable for you. This is left as an exercise to the reader.
PS: There seems to be one limitation: All types must be known before, for instance typeOf id
will fail.
Upvotes: 3