Mainly, what we want is a collection of ways to create new data, retrieve it, and modify or delete it. Most of the time, it doesn’t matter what is handling these operations, so that’s a dependency we should try to abstract out. We’ll also need a way to ensure that destructive updates are done in the right order, so monads might be a useful thing to use here too. We might try something like this: a type class Storage that provides basic storage facilities for data that is serializable (or mappable to some database representation), using globally unique identifiers (GUIDs) to reference particular data. The “store” operation might also want to check for validity of the data before saving it, though an alternative way is to design the business types so that invalid data is excluded from the types (simple example: that 123 would be rejected as a String). A sub-class could provide more powerful search facilities, taking some collection of predicates on some type and returning a list of GUIDs of the objects found. (Note that GUID type has one parameter: this will help to distinguish GUIDs for one type from those of another type, and so help to prevent a GUID for one record type being used to retrieve another. In contrast, most frameworks just use unprotected integers for their GUIDs.) class Monad s = Storage s where store :: (Serializable a, Validateable a) = a - s (GUID a) fetch :: Serializable a = GUID a - s a delete :: GUID a - s () class Monad s = Searchable s where search :: [PredsOn a] - s [GUID a] I envisage these storage facilities being used outside of the core business app, thus being used to store data that is computed by core functions, or to retrieve data that is used in core functions. Until proved otherwise, I’d like to keep this separation strict, and hence keep the business logic as simple as possible. Identifying the Interactions Let’s return to the question of how to convert the types and functions on the domain objects into a recognizable application, by considering what extra we have to add to make an application. One approach is to recognize that we’re wanting to construct various user stories and common tasks out of the basic domain operations. For example, allowing a user to create a new list with a name, and to have it saved if valid. Consider though, that there are certain stories we want to support, and some combination of actions that we do not. You might be thinking that we can use tests as a way to start distinguishing what is allowed and what isn’t, but we can be more functional here. What about representing this in types? One possibility is to encode allowed actions as a set of values in some type, for example the simple set of actions: data UserAction = CreateList { list_name :: String } | TickList (GUID List) | DeleteList (GUID List) | ... PragPub January 2013 11
Purchased by unknown, nofirst nolast From: Scampersandbox (scampersandbox.tizrapublisher.com)