Snap: A Haskell Web Framework
snapframework.com
snapframework.com
Other projects, take notes.
That's unfair. Documentation writing is usually the last item for software developers. Full stop.
But, as dons pointed out, crappy docs is not limited to Haskell by any means.
If you want to see your project gain traction it has to be stupid easy for people to jump in and start playing, and solid Web site with serious docs is a big win there (as are project generators and copious examples).
The Snap framework uses a style of I/O called “iteratee I/O”. We have opted for iteratees over handle-based and lazy I/O because iteratee I/O offers better resource management, error reporting, and composability.
If you're interested, you can read some in-depth stuff here: http://okmij.org/ftp/Streams.html (these papers are somewhat famous)
The Snap tutorial has a decent summary of how they work, but it doesn't provide a lot of context for why.
If you are already familiar with functional programming, then you know about foldl, foldr, and left/right recursion. When processing a collection of some kind (like a list), usually you take an enumerator (the fold function or a map function, for example) and pass it an iteratee (the function that will be applied to each element) and then your work gets done and you get a result. Left fold is non-lazy, right fold and map are lazy.
Because Haskell is a lazy language, there is another way to process lists lazily: with explicit recursion to the right. If you are familiar with Scheme or Lisp, it would look kind of like
(define (myfunc xs)
(cond ((null? xs) '())
(else (cons (+ (car xs) 1)
(myfunc (cdr xs))))))
which would add 1 to each element of a list, returning a new list.In Lisp or a non-lazy Scheme, this would blow up in your face for a large list. In Haskell it's no sweat, because the list will only be processed as it is needed, and generally for control structures you have to go out of your way to make it 'space leak' (build up a bunch of unevaluated expressions and then explode when you finally evaluate it.)
If you use foldr or map in Haskell, it works like this. One interesting thing about using non-explicit recursion in Haskell is that if you put a bunch of maps or folds (or with the Stream Fusion library, many different types of list operations) next to each other, the compiler will inline them together and eliminate intermediate list allocations. So you can end up writing your process as a series of discrete logical steps (from list type a, to b, to c, to d) but still get the performance of doing only one traversal. Very nice.
One of the downsides is that you end up with a lazy control structure, which can be problematic when dealing with the real world. For example, when reading from a file handle, someone could move the file or delete it or something. With lazy IO, the programmer is not really controlling when the file will be read by the program. Often, this is not a problem. However, with networking and time-critical applications, it can be a big problem.
Another problem is that if you need to perform allocations (like going from a list of one length to a list of another length) in the middle of your transformations, it gets really tricky if you want to maintain fusion. Normally with explicit right recursion you can just cons together a larger list on the fly, but we can't do fusion on explicit recursion, and compilers are generally bad about figuring out what kinds of allocation our explicitly recursing function does.
We don't want to give up our elegant way of expressing operations of sequences, but we want to know when we are writing and reading from the real world, and we want to do it with determinate constant space. Iteratees let us do that almost as elegantly as the normal list stuff by driving the consuming of the list via the functions that do the actual processing. Sort of like a list unfold, but not quite.
Check out attoparsec-iteratee http://hackage.haskell.org/package/attoparsec-iteratee for an example of iteratee usage: it turns a fast bytestring parser into one that's based on iteratees, which will probably end up being even faster. (correction: which will allow you to process streams in constant space without needing to explicitly manage things.)
(If Dons or Gregory Collins is around to correct the numerous mistakes I'm sure I made in this post, please do so!)
Iteratees don't really make things "faster" (you end up with a sequence of imperative read()/write() calls just like everything else), but they allow you to stream in O(1) space while still being easily composable.
For example, we do gzip, buffering, & chunked transfer encoding using simple function calls, and we don't really have to worry about explicit buffer management or control flow while we're doing it.
req <- getRequest
writeBS $ maybe "" id (getParam "s" req)
with param <- getParam "s"
writeBS $ maybe "" id paramNo, that's for a production website and requires setup. They just need to replace webrick with thin.
> gem install thin
then
> thin start
instead of
> ./script/server
Simple.
script/server -e production
Running in development mode severely hampers Rails' performance.
Better would be to just limit the test to single-process/single-thread. The idea should be to compare web frameworks not web servers.
> thin -e production -s 4 start
The problem is that each process listens on its own port. They can't all listen on port 3000, so you need some sort of proxy to parcel out the requests.
httperf --hog --num-conns 1000 --num-calls 1000 --burst-length 20 --port 3000 --rate 1000 --uri=/pong
The best I got was 258 requests / sec and 13.5 average responses / sec. The chart on the website plots responses / sec. So thin seems to perform worse. I'm guessing that we're running into the limit of 1024 file descriptors. I didn't test with the libev version of Snap, so my testing had that limit too, but my guess is that Snap is faster and manages to stave off the limit long enough to serve a reasonable number of requests. But I don't know for sure.
But I haven't seen what other options are in place for simple templating. Presumably you could use Hamlet or something, if you wire it in.
It should include at least some basic string interpolation stuff so you can so simple output in a simple way.
I am confused as well.
;; QUESTION SECTION:
;www.snapframework.com. IN A
;; ANSWER SECTION:
www.snapframework.com. 3600 IN CNAME herod.hosts.coptix.com.
herod.hosts.coptix.com. 86400 IN A 64.203.102.17
;; AUTHORITY SECTION:
coptix.com. 108393 IN NS a.ns.coptix.com.
coptix.com. 108393 IN NS b.ns.coptix.com.
;; ADDITIONAL SECTION:
a.ns.coptix.com. 194793 IN A 217.160.248.220
b.ns.coptix.com. 205469 IN A 64.203.102.2;; QUESTION SECTION: ;www.snapframework.com. IN A
;; ANSWER SECTION: www.snapframework.com. 3600 IN CNAME herod.hosts.coptix.com. herod.hosts.coptix.com. 86400 IN A 64.203.102.17
but it works fine now, after a couple of hours i was able to access the snapframework.com
still have no idea what it was - never happened before :/
Think of a monad as a spacesuite full of nuclear waste in the ocean next to a container of apples. now, you can't put oranges in the space suite or the nucelar waste falls in the ocean, but the apples are carried around anyway, and you just take what you need. (http://koweycode.blogspot.com/2007/01/think-of-monad.html)
That explanation is pretty good if you understand monads. Otherwise it is completely useless.
What are the apples and oranges supposed to represent? Why would anyone want to put oranges in the spacesuit? When "you just take what you need", who is the "you", and are they swimming around in the ocean? Is the ocean significant at all?
I might be wasting my time trying to understand this analogy. I'm not up to speed on terms Haskellers throw around such as fields, rings, category theory, etc. Haskellers are welcoming of newbies and eager to teach, but are on a vastly different level than the average developer causing some of us to feel we're not smart enough to wield Haskell. Unfortunately even some basic concepts are so abstract that trying to explain them in concrete terms often leads to gibberish such as dons' analogy there.
I realize dons probably wrote that for the monad-savvy crowd as kind of an inside joke, perhaps I'd do well to just move on.
http://en.wikibooks.org/wiki/Haskell/do_Notation
Actually, this is just a special case of a general rule: "The best way to understand {{programming_concept}} is to stop reading {{programming_concept}} tutorials and write some code."
For example, in a language like C, the active part of the program is a composition of statements that are "executed" one by one and in order. The IO monad in effect implements this strategy.
The big thing is that the monad abstraction allows for other composition strategies too. The Maybe monad for example could be compared to a language that keeps executing things one by one until one of the computations fails, which results in the entire computation yielding a null result.
The type system helps keep these different strategies separate from each other, so that eg. one can't execute in parallel operations that require sequential composition.