Big trouble The bottleneck of static typing is heterogeneous collections and variadic functions. Therefore, in the RPC-libraries, there is often an approach when the incoming data is just one ADT-piece, and for methods one is the same flat type "[Foo] -> IO Foo", the implementations of which copy-paste deserialization / serialization, which is inconvenient and causes errors including runtime.
The solution to this problem bothered me almost from the very beginning of the practical application of Haskel by me and, finally, last night, I was inspired by as much as 6.5 milliolegas and after the session of divination from mistakes and conversations with ghci, I succeeded.

')
Suppose we want to make a list / dictionary Text -> Method. And immediately bummer:
methods :: [(String, ????)] methods = [ ("add", \xy -> return (x + y)) , ("reverse", \s -> return (reverse s)) ]
All methods have a different type. This can be solved by putting them in an opaque box.
data BaconBox = forall a. a methods :: [(String, BaconBox)]
But such a box can not be unpacked. it is not clear what you can do with them, unpacked. This means that we need a class of types that can be in it and describing how to bring the function to a normal form, i.e. functions ready for deserialization of input data and serialization of the result.
In this example, the “protocol” Read / Show will be used. Not the best, but for the rest all the same. The data type, respectively, will be String.
class Tossable t where toss :: [String] -> t -> IO String data BaconBox = forall a. (Tossable a) => BaconBox a
With this definition, it is already clear even to the compiler that any type can be put inside the box, for which the toss function is defined, which is ... Bach! Here we smoothly go to the second problem. After all, it would be desirable, that the called methods could have any arguments in any quantity. Those. so that you can simply take and put into the box any RPC handler for which marshaling procedures are defined.
Oleg and the Haskal wiki have an example of something similar - printf with any number of arguments of any type:
www.haskell.org/haskellwiki/Varargs . But it is not quite that. But not entirely wrong! awesome.gif
The trick is based on two instances - the basic form and the folding of arguments.
The basic form determines what to do when the data is collected. It also defines the class of the output type of the method.
instance (Show a) => Tossable (IO a) where toss [] f = fmap show f toss _ _ = fail " "
If all arguments are used and we have ready-made values in our hands (in this case, this is an action that needs to be performed), i.e. All arguments are applied to the function - we execute it and immediately serialize the result. The remaining arguments can be thrown away, and you can curse - I prefer to throw an error than to guess about correctness.
To convolve the arguments, a very interesting instance is used, demonstrating in all its glory the powerful functional approach in general and the correct type system in particular. Here the class of input arguments is fixed, which guarantees the
success of the presence of a de-sterilizer for the type used in the RPC method call.
instance (Read a, Tossable t) => Tossable (a -> t) where toss [] _ = fail " " toss (a:as) f = toss as (f (read a))
Despite the magical definition, what is happening is rather trivial. If our type is a function (a -> t), and we still have arguments for it, then we will deserialize the next argument according to the type that the function expects and apply it to it.
If the result is in the form of the base form - well, if after applying the argument to the function, the function is again obtained, we repeat the procedure.
doAnd :: Bool -> Bool -> IO Bool doAnd ab = return (a && b) doSum3 :: Double -> Double -> Double -> IO Bool doSum3 xyz = return (x + y + z) main = do toss [] (doAnd True True) >>= print toss ["True"] (doAnd True) >>= print toss ["True", "True"] doAnd >>= print toss ["42", "2.71828", "3.14159"] doSum3 >>= print
Et voila! The function quietly unpacks and feeds the arguments and packages the result.
The final touch is the dynamic dispatching of methods from the boxes. To do this, add some metadata to our box and a search function for them:
data BaconBox = forall a. (Tossable a) => BaconBox String a tossBacon :: (Show a) => [BaconBox] -> String -> [String] -> IO a tossBacon [] _ _ = fail " " tossBacon (BaconBox bn bf : bs) name args | bn == name = toss args bf | otherwise = tossBacon bs name args
A special function is needed so that the compiler does not explode the brain (sic!) When opening an existential container in any way other than pattern-matching. FP-world problems ...
So, we have a container that allows us to poke in functions with an arbitrary (but strictly limited in input and output) type and call them by name, automatically doing the marshaling routine. Less code - less bugs. Hooray!