I'm trying to find a way to be able to get name of my datatype constructor as String
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
I'm searching something in form of name :: Constructor Test -> String
which can be used like:
name Lol -- "Lol"
name Foo -- "Foo"
name Lel -- "Lel"
Closest what I was able to achieve was:
module Main where
import Data.Typeable
import Data.Data
data Test
= Foo
{ a :: Int
, b :: Int
}
| Bar
{ a :: Int
}
| Lol
| Lel String
deriving (Show, Data, Typeable)
main :: IO ()
main = do
print $ toConstr Lol
print $ toConstr $ Bar undefined
print $ toConstr $ Foo undefined undefined
but toConstr
excepts object as a argument instead of the constructor :/
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