tags:

views:

131

answers:

2

I'm currently working on a Haskell project which automatically tests some functions based on an XML specification. The XML specification gives the arguments to each function and the expected result that the function will provide (the arguments are of many different types). I know how to extract the function arguments from the XML and parse them using the read function, but I haven't figured out how to invoke the function using the arguments I get out.

What I basically want is to read and store the arguments in a heterogeneous list (my current thinking is to use a list of type Data.Dynamic) and then invoke the function, passing this heterogeneous list as its argument list. Is this possible? Modifying the functions under test is not an option.

A: 

Using Data.Dynamic is a bad idea, generally, as you sacrifice the ability of GHC to check arguments for you. Almost always you know the valid functions the types must support, so you can always use an existentially typed list.

Instead, is it possible to build a data type that represents the valid types that your XML will generate? That is, build an ADT that represents the abstract syntax of well-formed XML arguments.

For example, look at the JSON ADT: http://hackage.haskell.org/packages/archive/json/0.4.3/doc/html/src/Text-JSON-Types.html#JSValue

Alternatively, use an existential type to (statically) ensure that values support the methods you declare for them. For example, all values from your XML must support Show, perhaps, so you can describe the heterogeneous list as:

data XMLList = forall a. Show a => XMLList [a])

An example from the Haskell wikibook: http://en.wikibooks.org/wiki/Haskell/Existentially_quantified_types#Example:_heterogeneous_lists

Don Stewart
So how do I pass a list of this type as the argument list to a function?
Derek Thurn
The existential buys nothing here. XMLList is isomorphic to [String], so just use [String].
luqui
+2  A: 

I would recommend Nathan's suggestion of using Haskell as the spec language. Haskell itself is the best possible data format for working with Haskell functions :-)

But since it's an interesting question, I'll pretend that you, by some bizarre constraint, have to use XML. You are going to have to convert your XML to a real Haskell function somewhere. That means having some mapping:

lookupFunc :: String -> ???

Which looks up a func by name. You will have to write this mapping by hand, or generate it using Template Haskell. But importantly, ??? is not a type, and this function needs a real type.

Here's a neat one, similar to your heterogeneous list but more optimized to the problem at hand:

data SpecFunc = Result String | More (String -> SpecFunc)

This is your interface to the XML specification. It says that either I am done and already have a result (which has already been stringified) or I need another argument to continue (with the conversion from string baked into that function). (Geeky side note: this is called the "free monad over (String ->)" -- but the monadiness is quite irrelevant to us right now).

Now we can write a typeclass to convert Haskell functions into their SpecFuncs, if their types meet our criteria:

class HasSpecFunc a where
    toSpecFunc :: a -> SpecFunc

instance (Read a, HasSpecFunc b) => HasSpecFunc (a -> b) where
    toSpecFunc f = More (\input -> toSpecFunc (f (read input)))

... -- one of these for each one of your "primitive" result types
instance HasSpecFunc String where
    toSpecFunc x = Result (show x)

Using some evil you can avoid having to specify one instance per result type. At the top of the file, enable overlapping instances:

{-# LANGUAGE OverlappingInstances #-}

Then use:

instance (Show a) => HasSpecFunc a where
    toSpecFunc x = Result (show x)

Then you can call a SpecFunc with something like:

-- returns Nothing if the wrong number of arguments were provided
runSpecFunc :: SpecFunc -> [String] -> Maybe String
runSpecFunc (Result x) [] = Just x
runSpecFunc (More f) (x:xs) = runSpecFunc (f x) xs
runSpecFunc _ _ = Nothing

I hope that made sense. But again, ditching the XML and using Haskell instead is far preferable to this.

luqui
Of course, if your arguments and results have more structure than just being convertible from/to strings, substitute that for String and typeclasses for converting from/to that representation for Read/Show here.
luqui