Patternmatching on 'Or' of 2 newtypes in haskell

81 views Asked by At

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.

2

There are 2 answers

5
dfeuer On BEST ANSWER

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.

3
leftaroundabout On

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