Generic combination of generic types
Hi there, This is a question about combining data types in a generic way. I am using Haskell to write a first implementation of a new generic algorithm to decide a fairly large class of modal logics. Type classes provide an excellent way to generically define a modal language. In particular you only need to define the data type of the modal operator (e.g. it could be indexed by rationals) and also a special matching rule, which is used in the proof checker. However I also want to combine different modal languages to create new ones *in a generic fashion*. The "modal language" I'm talking about is just a data-type with a single type variable: data L a = F | T | Atom Int | Neg (L a) | And (L a) (L a) | Or (L a) (L a) | M a (L a) deriving (Eq,Show) // convention: clause([positive literals,negative literals]) newtype Clause a = Clause ([L a],[L a]) deriving (Eq,Show) with a type class like this: class (Eq a) => Logic a where match :: Clause a -> [[L a]] and instances like: data K = K deriving (Eq,Show) instance Logic K where match (Clause (pls,nls)) = ... data G = G Int deriving (Eq,Show) instance Logic G where match (Clause(pls,nls)) = ... data KD = KD deriving (Eq,Show) instance Logic KD where match (Clause (pl,nl)) = ... I have various such instances of logics and I want to combine them in particular ways. For example I might want to interlace three instances in a new language such as: data L1 = F1 | T1 | Atom1 Int | Neg1 L1 | And1 L1 L1 | Or1 L1 L1 | MCP L2 L3 deriving (Eq,Show) data L2 = F2 | T2 | Atom2 Int | Neg2 L2 | And2 L2 L2 | Or2 L2 L2 | MK L1 deriving (Eq,Show) data L3 = F3 | T3 | Atom3 Int | Neg3 L3 | And3 L3 L3 | Or3 L3 L3 | MKD L1 deriving (Eq,Show) or data L1 = F1 | T1 | Atom1 Int | Neg1 L1 | And1 L1 L1 | Or1 L1 L1 | MK L2 deriving (Eq,Show) data L2 = F2 | T2 | Atom2 Int | Neg2 L2 | And2 L2 L2 | Or2 L2 L2 | MKD L1 deriving (Eq,Show) ... the idea being that a different language(s) appear after the modalities (MCP, MK, MKD above). Put simply I want to take some data types and construct a new (specifically ordered) data type(s) defined in terms of these. I also want to be able to define recursive functions over this language, with which I'm having a problem (see below). I originally tried this by defining a language: data L a = F | T | Atom Int | Neg (L a) | And (L a) (L a) | Or (L a) (L a) | M a deriving (Eq,Show) Note the last term is "M a" not "M a (L a)" as in the first type. Using this 'a' I could write terms like: M (K,And F (M (KD,F))), i.e. use tupling to define in the first component the data type of the modality and in the second component (and third if considering binary modal operators) defining what comes after the modality. I found with this approach I was able to match these types e.g. note the "(K,a)" data K = K deriving (Eq,Show) instance Logic (K,a) where match (Clause (pl,nl)) = However, crucially, I cannot seem to define recursive functions because on each recursive step I am changing the type of terms considered. The underlying algorithm requires me to strip off a layer of modalities (e.g. M (K,F) goes to F) at each step. For example if I have M (K,M (KD,F)) this has type: L (K,L (KD,L a)), if I pass to the subterm M (KD,F), then this has type: L (KD,L a). Thus Haskell could not resolve this type ('unification would produce infinite data type'). However this method seems very attractive because it only needs one data type to define many different languages. I've tried alternate approaches, for example explicitly defining the interlaced language, such as: data L1 = F1 | T1 | Atom1 Int | Neg1 L1 | And1 L1 L1 | Or1 L1 L1 | MCP L2 L3 deriving (Eq,Show) data L2 = F2 | T2 | Atom2 Int | Neg2 L2 | And2 L2 L2 | Or2 L2 L2 | MK L1 deriving (Eq,Show) data L3 = F3 | T3 | Atom3 Int | Neg3 L3 | And3 L3 L3 | Or3 L3 L3 | MKD L1 deriving (Eq,Show) However this becomes messy because I then need to define all of my operations (e.g. find conjunctive normal form) on these new constructors. It seems that I need to use the actual constructor to pattern match... I can't use some ADT functions. I've also tried the first method above with 2 type variables, with the same problems (unification of infinite data type). Does anyone have any ideas? Frankly I'd be impressed if someone even takes the time to read though this. Thanks, Rob
I'm not familiar with modal logic, but if modalities stack, you might be able to do something like this. -- Garrett Mitchener {-# OPTIONS_GHC -fglasgow-exts #-} module Test where data L a = F | T | Atom Int | Neg (L a) | And (L a) (L a) | Or (L a) (L a) | M (L a) -- the modality a is a phantom type for this deriving (Read, Show, Eq) -- Actually I never use the data constructors, just the types, so you could leave them out. data NullModality = NullModality deriving (Read, Show, Eq) data KModality a = KModality a deriving (Read, Show, Eq) data KDModality a = KDModality a deriving (Read, Show, Eq) -- Need multi-parameter type classes for this: class StripModality m1 m2 | m1 -> m2 where stripModality :: L m1 -> L m2 stripModality F = F stripModality T = T stripModality (Atom n) = Atom n stripModality (Neg p) = Neg (stripModality p) stripModality (And p q) = And (stripModality p) (stripModality q) stripModality (Or p q) = And (stripModality p) (stripModality q) stripModality (M p) = M (stripModality p) instance StripModality NullModality NullModality instance StripModality (KModality m) m instance StripModality (KDModality m) m expr :: L (KDModality (KModality NullModality)) -- for modality KD on top of K expr = And (Atom 1) (M (Atom 2)) -- This is kind of dumb but it shows something this can do listMAtoms :: StripModality m1 m2 => L m1 -> [L m2] listMAtoms p@(Atom n) = [stripModality p] listMAtoms (Neg p) = listMAtoms p listMAtoms (And p q) = listMAtoms p ++ listMAtoms q listMAtoms (Or p q) = listMAtoms p ++ listMAtoms q listMAtoms (M p) = listMAtoms p listMAtoms _ = []
participants (2)
-
Garrett Mitchener -
Rob Myers