tags:

views:

177

answers:

2

I'm thinking about ways to use Haskell's type system to enforce modularity in a program. For example, if I have a web application, I'm curious if there's a way to separate all database code from CGI code from filesystem code from pure code.

For example, I'm envisioning a DB monad, so I could write functions like:

countOfUsers :: DB Int
countOfUsers = select "count(*) from users"

I would like it to be impossible to use side effects other than those supported by the DB monad. I am picturing a higher-level monad that would be limited to direct URL handlers and would be able to compose calls to the DB monad and the IO monad.

Is this possible? Is this wise?

+1  A: 

Thanks for this question!

I did some work on a client/server web framework that used monads to distinguish between different exection environments. The obvious ones were client-side and server-side, but it also allowed you to write both-side code (which could run on both client and server, because it didn't contain any special features) and also asynchronous client-side which was used for writing non-blocking code on the client (essentially a continuation monad on the client-side). This sounds quite related to your idea of distinguishing between CGI code and DB code.

Here are some resources about my project:

  • Slides from a presentation that I did about the project
  • Draft paper that I wrote with Don Syme
  • And I also wrote my Bachelor thesis on this subject (which is quite long though)

I think this is an interesting approach and it can give you interesting guarantees about the code. There are some tricky questions though. If you have a server-side function that takes an int and returns int, then what should be the type of this function? In my project, I used int -> int server (but it may be also possible to use server (int -> int).

If you have a couple of functions like this, then it isn't as straightforward to compose them. Instead of writing goo (foo (bar 1)), you need to write the following code:

do b <- bar 1
   f <- foo b
   return goo f

You could write the same thing using some combinators, but my point is that composition is a bit less elegant.

Tomas Petricek
`import Control.Monad; (goo <=< foo <=< bar) 1` ... yeah, it is not quite as clean, but still not *too* too bad.
ephemient
Yeah, this was worse in F# where you also wanted to write method calls such as `o.Bar().Foo().Goo()` and there is no way to do this using combinators. Using `<=<` in Haskell seems OK, but still not perfect.
Tomas Petricek
+7  A: 

I am picturing a higher-level monad that would be limited to direct URL handlers and would be able to compose calls to the DB monad and the IO monad.

You can certainly achieve this, and get very strong static guarantees about the separation of the components.

At its simplest, you want a restricted IO monad. Using something like a "tainting" technique, you can create a set of IO operations lifted into a simple wrapper, then use the module system to hide the underlying constructors for the types.

In this way you'll only be able to run CGI code in a CGI context, and DB code in a DB context. There are many examples on Hackage.

Another way is to construct an interpreter for the actions, and then use data constructors to describe each primitive operation you wish. The operations should still form a monad, and you can use do-notation, but you'll instead be building a data structure that describes the actions to run, which you then execute in a controlled way via an interpreter.

This gives you perhaps more introspection than you need in typical cases, but the approach does give you full power to insspect user code before you execute it.

Don Stewart
Thanks, Don! The former solution sounds like what I'm looking for. Do you know any specific packages that use this technique, or good terms to google for ("restricted IO monad" didn't turn up much)?
Bill
A good example of the 'taint monad' concept, http://blog.sigfpe.com/2007/04/trivial-monad.html
Don Stewart
Thanks. If I choose to use the "tainted monad" pattern for my DB monad, what do I do to extract the data from the DB monad? Does my HTTP action handler have to use a monad transformer with DB in it?
Bill
Most monads come with a "run" command. The usual pattern is that monad type Foo has "runFoo :: Foo a -> a".On the other hand you may want to restrict the destination of your output, so you might have "runFoo :: Foo a -> Bar a"
Paul Johnson
Afterthought: Or if runFoo has to do IO then it would be "runFoo :: Foo a -> IO a". In your case it sounds like you want the only IO that CGI can cause to be databases, so "runDB :: DB a -> CGI a", and then have a top-level "runCGI :: CGI a -> IO a".
Paul Johnson
Also have a look at "Monads a la carte". (I *think* that's the title...)
Porges