Functional SOA
In the context of Service Oriented Architecture, a service is the conjuncture of an endpoint, a contract and a protocol. The endpoint is where you send messages to, the protocol determines how they get there, and the contract defines the semantics of the communication (understood as message-exchange; although with more complex services, especially those involving asynchronous messaging, questions of choreography begin to arise).
In a statically-typed language with reflection and annotation capabilities (e.g. C#), it is possible to derive all three aspects of a service from an annotated interface: the system can reflect over the interface, read off the annotations, generate appropriate message handlers and service definitions and hook these into a service broker (e.g. a web server that maps URIs to handlers). The interface is already a code object, a part of the language’s type system: the service can be instantiated on the server side by classes that implement that interface, and on the client side by proxy objects that dispatch method calls against that interface to the service endpoint via the specified protocol.
That’s how Microsoft’s .Net framework (and specifically Indigo) works. It makes use of type system information to construct both the plumbing (endpoint and protocol) and the public profile (contract) of a service. In effect, the IDL (interface description language) of systems such as COM and CORBA has migrated into the programming language via the type system: typed and annotated programs are thus self-describing, or at least interpretable by a service framework. Furthermore, this type information is available at run-time, and the type system permits values (objects) to be cast to the universal type (object), such that they can be treated solely as carriers of type information about themselves, and subsequently as targets for the “dynamic dispatch” of method calls. This makes it possible to write generic framework code that will work with any object that is passed to it, code that is solely concerned with the tasks of reflection and method invocation and consequently sits on the periphery of the normal compile-time-type-checked universe.
I’ve commented before on the suitability of pure functional programming languages for Service Oriented Architecture, the closeness of the fit between the functional idiom and SOA’s stateless façades. It should be evident, however, that the .Net approach will not work well in a language like Haskell, because no escape to the periphery of Haskell’s type system is permitted. We can perfectly easily construct a “universal” type in Haskell, and have values of that type carry type information around with themselves; but such a type would be “universal” only for the universe of types that we defined into it in advance (in other words, it would be polymorphic over a given range of types). Other kinds of sophisticated type-hackery are possible, and will give us the illusions of various kinds of freedom; but all involve the creation of sub-systems within the confines of Haskell’s basic type system, and force us to work within those subsystems whenever we need the flexibility they afford. (That’s just what we should expect from Haskell, of course! It’s the same philosophy that motivates the confinement of I/O side-effects to monads…)
I don’t want to dismiss the utility of some of the more hackish approaches to type-flexibility in Haskell, because it’s clear that some of them work very well. But I think that a more idiomatically Haskell-ish approach to SOA would not contort the type system in an attempt to replicate the mechanisms used by .Net, but instead make use of Haskell’s own particular strengths as a language. What I would suggest is that in Haskell the IDL should not be imported into the type system, from whence it would then have to be retrieved via some simulacrum of reflection, but should instead be implemented as combinator-based embedded language. Service definitions would then be values within the type system, constructed using this embedded language, that would be used in much the same way as the annotated interfaces obtained by reflection from .Net objects. This would provide the additional benefit of being able not only to inspect and interpret such values at run-time, but also to compose and transform them.
