Note that the constructors themselves have types:
Foo :: Int -> Int -> Test
Bar :: Int -> Test
Lol :: Test
Lel :: Strinng -> Test
so you're asking for a function name
that can take a constructor whose type matches any one of these "patterns" to produce a String
. If you wrote down the type signature for name
, it would need to look something like:
name :: (a1 -> a2 -> ... -> an -> Test) -> String
or, if we wanted to use it with any object, not just Test
, something like:
name :: (a1 -> a2 -> ... -> an -> finalObject) -> String
where the number of a
types depends on the arity of the constructor.
There's no straightforward way of writing such a function in Haskell. In fact, it's impossible in "plain" Haskell. However, with some extensions, it can be done using some type class trickery.
The extensions needed are:
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
The idea is to introduce a type class for the name
function:
class Name a where
name :: a -> String
and then introduce an instance that handles the case where a
still needs arguments by supplying undefined
to reduce the argument count by one:
instance Name (r -> a) where
name f = name (f undefined)
This instance will be used recursively. When we call name Foo
, it'll be used to reduce this to name (Foo undefined)
and then used again to reduce it to name (Foo undefined undefined)
. Since this final object doesn't match the pattern r -> a
, we'll be ready to use the default instance:
instance Name a where
name = show . toConstr
This code won't work as-is. We need to add some constraints in appropriate places and use an OVERLAPPING
pragma to handle these overlapping instances, but the final definition of the type class and its instances is:
class Name a where
name :: a -> String
instance {-# OVERLAPPING #-} Name a => Name (r -> a) where
name f = name (f undefined)
instance (Data a) => Name a where
name = show . toConstr
This works fine:
λ> name Foo
"Foo"
λ> name Bar
"Bar"
λ> name Lol
"Lol"
λ> name Lel
"Lel"
However, now that you have this function, I think you will discover that it's incredibly difficult to use in a real program.
Anyway, the full code follows. Note that modern versions of GHC don't need deriving Typeable
, so you can leave it out.
{-# LANGUAGE DeriveDataTypeable #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE UndecidableInstances #-}
module Constructor where
import Data.Data
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
deriving (Show, Data)
class Name a where
name :: a -> String
instance {-# OVERLAPPING #-} Name a => Name (r -> a) where
name f = name (f undefined)
instance (Data a) => Name a where
name = show . toConstr
main = do
print $ name Foo
print $ name Bar
print $ name Lol
print $ name Lel