Reputation: 161
I am using a decision diagram library within a haskell program. For this i want to declare 2 different (new)types that keeps track of which kind of decision diagram i am dealing with. The library i am using is cudd, and decision diagram base type from there is a DdNode, but my question is only haskell related.
newtype Bdd = ToBdd Cudd.Cudd.DdNode
newtype Zdd = ToZdd Cudd.Cudd.DdNode
Normally i want to discriminate between them when calling functions, but now i want to use a function which does not have to discriminate between the 2 types. i mainly tried to solve this in 3 different ways:
data Dd = ToBdd Bdd | ToZdd Zdd
printDdInfo :: Dd -> IO()
printDdInfo (ToZdd dd) = do
putStrLn "Hello, zdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (ToBdd dd) = do
putStrLn "Hello, bdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo :: Either Bdd Zdd -> IO()
printDdInfo (ToZdd dd) = do
putStrLn "Hello, zdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (ToBdd dd) = do
putStrLn "Hello, bdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo :: Either Bdd Zdd -> IO()
printDdInfo dd = case dd of
Zdd dd -> do
putStrLn "Hello, bdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
Bdd dd -> do
putStrLn "Hello, bdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
All of these methods failed. What is the most elegant way of writing this code? Thanks for your attention.
Upvotes: 0
Views: 83
Reputation: 48581
I haven't dug too deeply into your code, but from your description it sounds like you might be interested in the idea of a phantom type.
newtype Dd x = ToDd (Cudd.Cudd.DdNode)
data B
data Z
Now you can distinguish between Dd B
and Dd Z
when you want, and work polymorphically with Dd x
when you don't care.
In modern GHC Haskell, if you want to indicate that B
and Z
are the only tags, you can use the DataKinds
and KindSignatures
extensions and do it like this:
newtype Dd (x :: DdTag) = ToDd (Cudd.Cudd.DdNode)
data DdTag = B | Z
In this context, you'll deal with Dd 'B
and Dd 'Z
, where the single quote (pronounced "tick") "promotes" the data constructor to the type level.
To write a function that behaves differently depending on which tag the type has, you'll need a class.
class Zoop tag where
zoop :: Dd tag -> Int
zeep :: Char -> Dd tag
zaaaaap :: Dd tag -> Dd tag
Now you can write a Zoop
instance for B
(or 'B
) and one for Z
(or 'Z
) to let users use the method for either. Remember, though, that types are erased in compilation, so you'll need a Zoop a
constraint anytime you want to apply these methods with polymorphic tags.
Upvotes: 5
Reputation: 120711
I'd go with something very similar to your first approach, but you need to rename the constructors different from the newtype ones:
data Dd = FromBdd Bdd | FromZdd Zdd
printDdInfo :: Dd -> IO()
printDdInfo (FromZdd (ToZdd dd)) = do
putStrLn "Hello, zdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
printDdInfo (FromBdd (ToBdd dd)) = do
putStrLn "Hello, bdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
Alternatively, knowing that all the different dd-types share the same underlying representation, you could also forego storing the newtypes and instead set up Dd
independently:
data DdKind = IsBdd | IsZdd
data Dd = Dd { ddKind :: DdKind
, ddImplementation :: Cudd.Cudd.DdNode }
printDdInfo :: Dd -> IO()
printDdInfo (Dd ddk dd) = do
putStrLn $ case ddk of
IsBdd -> "Hello, bdd!"
IsZdd -> "Hello, zdd!"
Cudd.Cudd.cuddPrintDdInfo manager dd
Upvotes: 1