I would just declare your datatype an instance of the type class Show:
data Exp a = Const a | Eq (Exp a) (Exp a)
instance (Show a) => Show (Exp a) where
show (Const a) = show a
show (Eq x y ) = "[ " ++ show x ++ " , " ++ show y ++ " ]"
Look what happens when you load this in ghci and do:
*Main> let x = Eq (Const 1) (Eq (Const 2) (Const 3))
*Main> x
[1 , [2 , 3] ]
Answering comment:
You can easily deal with different types. Suppose you want to parse mathematical expressions. You can have the following structure, for example:
data Expr = Var String | Sum (Expr) (Expr) | Number Int | Prod (Expr) (Expr)
This is enough to represent any expression made of sums and products of numbers and named variables. For example:
x = Sum (Var "x") (Prod (Number 5) (Var "y"))
represents: x + 5y
To print this beautifully I'd do:
instance Show Expr where
show (Var s) = show s
show (Sum x y) = (show x) ++ " + " (show y)
show (Prod x y) = (Show x) ++ (show y)
show (Number x) = show x
This would do the trick. You could also use GADTs:
data Expr where
Var :: String -> Expr
Sum :: Expr -> Expr -> Expr
etc... and then instantiate this as Show.