Unfortunately, such a generic approach to constructors is impossible directly, but even if it was yours, it will not work - the extractLispVal function extractLispVal not have a clearly defined type, since the type of the result depends on the input value. There are various kinds of advanced hacking type systems that can do something like this, but this is actually not what you would like to use here.
In your case, if you are only interested in extracting certain types of values, or if you can convert them to one type, you can write, for example, a function of type extractStringsAndAtoms :: LispVal -> Maybe String .
The only way to return one of several possible types is to combine them into a data type and pattern matching on this: the general form of this creature is Either ab , which is either a or b , different constructors. You can create a data type that allows you to retrieve all possible types ... and it would be almost the same as LispVal itself, so it did not help.
If you really want to work with various types outside of LispVal , you can also look at the Data.Data module, which provides some tools for reflecting data types. I doubt that really is what you want.
EDIT : Just a little reflection, here are some examples of extraction functions you can write:
Create functions to extract a single constructor, as in Donβs first example, suppose you already know which constructor was used:
extractAtom :: LispVal -> String extractAtom (Atom a) = a
This will lead to runtime errors if applied to anything other than the Atom constructor, so be careful with this. In many cases, however, you know what at some point in the algorithm you have, so it can be used safely. A simple example would be if you have a LispVal list from which you filter out every other constructor.
Create safe single constructor highlight functions that serve as "Do I have this constructor?" predicate and "if yes, give me the contents" extractor:
extractAtom :: LispVal -> Maybe String extractAtom (Atom a) = Just a extractAtom _ = Nothing
Note that this is more flexible than the higher, even if you are sure which constructor you have. For example, this simplifies the definition of:
isAtom :: LispVal -> Bool isAtom = isJust . extractAtom assumeAtom :: LispVal -> String assumeAtom x = case extractAtom x of Just a -> a Nothing -> error $ "assumeAtom applied to " ++ show x
Use the syntax of the notation when defining a type, as in Donβs second example. This little language magic, for the most part, defines a bunch of partial functions like the first extractAtom above, and gives you a fantastic syntax for constructing values. You can also reuse names if the result is of the same type, for example. for Atom and String .
However, the fancy syntax is more useful for records with many fields, rather than with many single-field constructors, and safe extraction functions are generally better than errors that create errors.
Getting more abstract, sometimes the most convenient way is to have a single universal function of deconstruction:
extractLispVal :: (String -> r) -> ([LispVal] -> r) -> ([LispVal] -> LispVal -> r) -> (Integer -> r) -> (String -> r) -> (Bool -> r) -> (Double -> r) -> LispVal -> r extractLispVal f _ _ _ _ _ _ (Atom x) = fx extractLispVal _ f _ _ _ _ _ (List xs) = f xs ...
Yes, it looks awful, I know. An example of this (for a simpler data type) in standard libraries is the maybe and either functions, which deconstruct the types of the same name. In fact, this is a function that confirms the conformity of templates and allows you to work with it more directly. It may be ugly, but you only need to write it once, and it may be useful in some situations. For example, here you can do one of the following functions:
exprToString :: ([String] -> String) -> ([String] -> String -> String) -> LispVal -> String exprToString fg = extractLispVal id (f . map recur) (\xs x -> g (map recur xs) $ recur x) show show show show where recur = exprToString fg
... i.e. A simple recursive print function, parameterized by how to combine list items. You can also write isAtom etc. Easy:
isAtom = extractLispVal (const True) no (const no) no no no no where no = const False
On the other hand, sometimes what you want to do is a match with one or two constructors, with nested pattern matches and all the cases for constructors that you don't need. This is exactly what is best suited to match the pattern, and all of the above methods will simply complicate the situation. Therefore, do not tie yourself to one approach!
source share