Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

"However, I think this is one of those cases where OOP has a name for something that FP just takes for granted: function composition. Regardless, Clojure's Rack equivilant does provide middleware as functions of request -> response maps."

I'd like to address some of the finer points in the comparison of middleware vs function composition that have been overlooked.

First class functions - While Ruby does indeed have lambda's and blocks it does not have first class functions, or partial application. This makes state and argument management much more difficult. That is, Vagrant would have to cram _everything_ into `env`...

State - The middleware(s) operate on a common state container `env` and this is where the initial composition comparison arises. The common argument is consistently the `env`. The problem with cramming everything into `env` is that you discard data encapsulation. All the things are known to all the people, which is unnecessary and can cause implicit dependencies between middleware(s). By instantiating a class with values specific to its operation we avoid this issue all together. To achieve a similar result with pure functions you would need something like partial application which Ruby simply doesn't support. It's also worth noting that with pure functions you could adopt a monadic pattern for composing and managing the state which is, IMO, a more accurate analog [1].

Method dependencies - With regular functions you have two choices, you can simply use a global reference to a helper function to maintain discrete testable operations or inline all the different bits of functionality into the function itself thereby making it difficult to test. Generally speaking I'm a fan of keeping those helpers namespaced and easily sharing the small bits a state specific to the instance's operation between them via context. Keep in mind that small classes with sparse state access through the implicit self can actually be more readable, and the ability of the reader to reason about execution isn't hindered too horribly by the implicit state.

Warden - Implementing a recovery pattern is hard using just pure functions. In Vagrant a watcher middleware (Warden) is inserted between each middleware to catch exceptions. When an exception is thrown, because the parent is the Warden it can then recognize its place in the stack and work backwards calling a `recover` method on all the preceding middleware in the stack. This means that each middleware is effectively two methods. One for the initial operation and a second for recovering in the case of failure. You can implement something _similar_ using Either, but in reality you need a custom record data type that carries both functions in which case you're not composing a simple function any more and the difference is really just that Vagrant is written in Ruby. I've written a blog post on the inspiration for Vagrant's Warden implementation if you're interested [2].

Vagrant's Middleware != Rack - PEP333 makes no assertions about state cleanup because in an http request there really isn't much state (the prescription is to return a helpful error message[3]). Vagrant on the other hand is writing hundreds of MB to the disk and needs to clean up when something goes wrong, so I'm not sure that drawing a comparison with a Rack implementation makes sense.

1. http://johnbender.us/2010/07/22/middleware-composition-and-m...

2. http://johnbender.us/2010/10/18/haskell-and-vagrants-middlew...

3. http://www.python.org/dev/peps/pep-0333/#error-handling



Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: