I just noticed today that one of the tbnl's best features is missing in hunchentoot.
Previously (in tbnl) if you set *debug-mode* to T, the top level dynamic vars *request*, *reply*, etc will be bound to the last request, reply objects.
This is very handy and in line with Lisp's interactive development model.
I usually setup an empty handler and point the browser to that URL.
Now I can work with the *request* / *reply* objects in the REPL, and write html generation and GET/POST handling code interactively.
For instance, I can type these in the REPL
(setf hunchentoot::*session* (hunchentoot::session-verify *request*))
(session-value :selected-product)
"T-Shirt XXL"
(* (product-price *) (session-value :quantity))
15
and so on.
Once I'm satisfied with the result, I put all the code into the handler.
(defun product-total-page (&optional (request *request*)) (let ((p (session-value :selected-product)) (q (session-value :quantity))) (with-html (:html (:body (:table (:tr (:td "Product") (:td (str p))) (:tr (:td "Quantity") (:td (str q))) (:tr (:td "Total") (:td (str (* q (product-price p)))))))))))
This is also very handy for debugging (inspect *request*'s slots - cookies, header and whatnot).
Thoughts?
-- Mac
On Thu, 3 May 2007 01:14:54 -0700, "Mac Chan" emailmac@gmail.com wrote:
I just noticed today that one of the tbnl's best features is missing in hunchentoot.
Previously (in tbnl) if you set *debug-mode* to T, the top level dynamic vars *request*, *reply*, etc will be bound to the last request, reply objects.
This is very handy and in line with Lisp's interactive development model.
I usually setup an empty handler and point the browser to that URL.
Now I can work with the *request* / *reply* objects in the REPL, and write html generation and GET/POST handling code interactively.
For instance, I can type these in the REPL
(setf hunchentoot::*session* (hunchentoot::session-verify *request*))
(session-value :selected-product)
"T-Shirt XXL"
(* (product-price *) (session-value :quantity))
15
and so on.
Once I'm satisfied with the result, I put all the code into the handler.
(defun product-total-page (&optional (request *request*)) (let ((p (session-value :selected-product)) (q (session-value :quantity))) (with-html (:html (:body (:table (:tr (:td "Product") (:td (str p))) (:tr (:td "Quantity") (:td (str q))) (:tr (:td "Total") (:td (str (* q (product-price p)))))))))))
This is also very handy for debugging (inspect *request*'s slots - cookies, header and whatnot).
Thoughts?
I removed these variables when porting TBNL because it turned out that I never used them and I had never heard from anyone else who used them. It seems I misjudged... :)
However, can't you have the same result if you add (BREAK) to your empty handler and set *CATCH-ERRORS-P* to NIL? I think that *CATCH-ERRORS-P* is a superior replacement to *DEBUG-MODE* as you don't have to rely on your clients to not send more than one request.
Cheers, Edi.
On 5/3/07, Edi Weitz edi@agharta.de wrote:
However, can't you have the same result if you add (BREAK) to your empty handler and set *CATCH-ERRORS-P* to NIL? I think that *CATCH-ERRORS-P* is a superior replacement to *DEBUG-MODE* as you don't have to rely on your clients to not send more than one request.
You are right. If I were to debug a problem, *CATCH-ERRORS-P* and break would be sufficient.
And I lied, I didn't use the REPL all that often :-)
It's ok for a simple function call but for sexp > 2 level deep I prefer to use slime-scratch or work in the buffer directly and send sexp with C-x C-e. It is much easier this way because I dont need to recall history from the REPL and I have paredit and all the emacs goodies that don't work in the REPL.
What really bother me is that a lot of code can't be call / test interactively if any of the special variables are used (unbound error).
Anyway, I have a simple work around now. This kind of stuff is really easy with Lisp :-)
Thanks, -- Mac
Scribit Mac Chan dies 03/05/2007 hora 01:14:
Previously (in tbnl) if you set *debug-mode* to T, the top level dynamic vars *request*, *reply*, etc will be bound to the last request, reply objects.
[...]
Thoughts?
This is something I usually do myself, and I save this kind of objects in debug-specific variables. The *debug-mode* behaviour won't scale, because you have to be sure there will only be a single HTTP request sent to the server, or the various variables will be overwritten.
Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
Scalably, Pierre
I would really vote for the feature. I didn't know it existed before. Also the (break) doesn't always work well with slime environment (at least in my case) and it doesn't go well with the lisp flowy programming model (so you can build all the functionality from REPL using functions as building blocks).
Andrew
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
Scribit Mac Chan dies 03/05/2007 hora 01:14:
Previously (in tbnl) if you set *debug-mode* to T, the top level dynamic vars *request*, *reply*, etc will be bound to the last request, reply objects.
[...]
Thoughts?
This is something I usually do myself, and I save this kind of objects in debug-specific variables. The *debug-mode* behaviour won't scale, because you have to be sure there will only be a single HTTP request sent to the server, or the various variables will be overwritten.
Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
Scalably, Pierre -- nowhere.man@levallois.eu.org OpenPGP 0xD9D50D8A
-----BEGIN PGP SIGNATURE----- Version: GnuPG v1.4.6 (GNU/Linux)
iD8DBQFGOcTcxe13INnVDYoRAg4VAKDBoE3rkhpJhAnwRLjm14NlIonAhwCfWDrf y4qWWj0+lFP4nyCz7OkBGDQ= =cHRq -----END PGP SIGNATURE-----
tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
Scribit Andrei Stebakov dies 03/05/2007 hora 08:25:
I would really vote for the feature.
Bear in mind that it goes in the critical path WRT performance. Each if form in the code serving data and each cache msis coming with it makes Hunchentoot a tiny bit less performant.
As it is a debugging tool that can only be used in a very limited set of cases, and that general purpose and scalable solutions can be done in a few lines outside Hunchentoot's core, I don't think it's worth it.
Quickly, Pierre
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
... Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
Scalably, Pierre
I do this as well, but the debug-mode thing actually sounds very handy (of course, it's not useful for debugging running applications). Rob.
I found *debug-mode* useful as well.
Dan
On 5/3/07, Robert Synnott rsynnott@gmail.com wrote:
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
... Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
Scalably, Pierre
I do this as well, but the debug-mode thing actually sounds very handy (of course, it's not useful for debugging running applications). Rob. _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
Hi Pierre,
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
The *debug-mode* behaviour won't scale, because you have to be sure there will only be a single HTTP request sent to the server, or the various variables will be overwritten.
While in development, I'll be the only person sending http request, so this is a controlled environment. But this is really not the issue though. For debugging what Edi suggested is the way to go.
Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
No this won't solve the issue that I brought up.
If you test your handler in the REPL, it will complain that *response* is not bound.
Your *leak* object won't do any good here, you'll have to manually bind *request* to *leak* (and you can't automate this in your handler because at that time *request* is dynamically bound and you cannot set the global value of *request*)
But yeah there are other ways to do it. I just bring this up to see if this is something that other hunchentoot users would want to have built-in.
Regards, -- Mac
< But yeah there are other ways to do it. I just bring this up to see if this is something that other hunchentoot users would want to have built-in >
As mentioned, I did like having it in before. I tend to work in the REPL the way you describe. I'm curious: when you say "there are other ways to do it," what do you have in mind?
Dan
On 5/3/07, Mac Chan emailmac@gmail.com wrote:
Hi Pierre,
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
The *debug-mode* behaviour won't scale, because you have to be sure there will only be a single HTTP request sent to the server, or the various variables will be overwritten.
While in development, I'll be the only person sending http request, so this is a controlled environment. But this is really not the issue though. For debugging what Edi suggested is the way to go.
Just do it yourself, with a handler "leaking" the objects you want to inspect:
(defvar *leak*)
(defun leaking-handler () (setf *leak* (list *request* *reply*)) "<html><head><title>Leaked</title></head><body>Leaked.</body></html>")
No this won't solve the issue that I brought up.
If you test your handler in the REPL, it will complain that *response* is not bound.
Your *leak* object won't do any good here, you'll have to manually bind *request* to *leak* (and you can't automate this in your handler because at that time *request* is dynamically bound and you cannot set the global value of *request*)
But yeah there are other ways to do it. I just bring this up to see if this is something that other hunchentoot users would want to have built-in.
Regards, -- Mac _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
On 5/3/07, Daniel Gackle danielgackle@gmail.com wrote:
I'm curious: when you say "there are other ways to do it," what do you have in mind?
Dan
With this ugly bit of code. The debug-value macro is still nicer though :-)
(let ((saved-process-request #'hunchentoot::process-request) last-request last-session)
(defun request-sniffer (request) (setf last-request request last-session *session*) (values))
(defun enable-debug-mode () (push #'request-sniffer *dispatch-table*) (setf (fdefinition 'hunchentoot::process-request) #'(lambda (&rest args) (prog1 (apply saved-process-request args) (setq *request* last-request *session* last-session)))))
(defun disable-debug-mode () (makunbound '*request*) (makunbound '*session*) (setf last-request nil last-session nil *dispatch-table* (delete #'request-sniffer *dispatch-table*) (fdefinition 'hunchentoot::process-request) saved-process-request)))
Scribit Mac Chan dies 03/05/2007 hora 10:46:
Just do it yourself, with a handler "leaking" the objects you want to inspect [...]
No this won't solve the issue that I brought up.
You're right. I thought about it and the solution seemed too simple for me not to try and code it cleanly. I've published the code under MIT license here (that's a Mercurial repository):
http://arcanes.fr.eu.org/~pierre/2007/05/cl-leak/
Having an ASDF file is overkill, but at some point I thought I'd need once-only from cl-utilities...
Basically, you'd use leak-variables as needed in a handler, and it would store variables along with their names, so you can bind them to use them later with with-leaked-variables:
(define-leak-variable *leak*)
(defun handler-to-debug () (leak-variables *leak* *request* *reply* *session*) (do-some-hairy-stuff))
Then you trigger it from your browser, and you have an association list in *leak* that you can use to bind the variables leaked:
(with-leaked-variables *leak* (handler-to-debug))
In a production environment, you could use leak-variables-once.
But yeah there are other ways to do it. I just bring this up to see if this is something that other hunchentoot users would want to have built-in.
What bothers me in *debug-mode* is that even when you don't use it, it might slightly slow down the server.
Quickly, Pierre
Hi Pierre,
On 5/3/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
You're right. I thought about it and the solution seemed too simple for me not to try and code it cleanly. I've published the code under MIT license here (that's a Mercurial repository):
Basically, you'd use leak-variables as needed in a handler, and it would store variables along with their names, so you can bind them to use them later with with-leaked-variables:
(define-leak-variable *leak*)
(defun handler-to-debug () (leak-variables *leak* *request* *reply* *session*) (do-some-hairy-stuff))
(with-leaked-variables *leak* (handler-to-debug))
I don't want to keep going back to this topic but I find it strange that you don't follow what my intention was because I thought that's how everyone else write lisp code in a bottom up style (paraphrase PG).
So using my previous toy example:
These are my sexp building blocks:
(session-value :selected-product) (session-value :quantity) (product-price (session-value :selected-product)) (* (session-value :quantity) (product-price (session-value :selected-product)))
What I want is to type these expressions in the REPL or in a buffer, eval them and check the results. If they work fine, I can put them together like LEGO and wrap them around a defun.
Now the problem is the dynamically bound variables.
Using your macro, my work flow would be:
(with-leaked-variables *leak* (session-value :selected-product))
(with-leaked-variables *leak* (session-value :quantity))
and then if they work, I need to cut the inner sexp and paste it into the defun, how inconvenient!
What's more, that's not even the way that I prefer to write code.
Instead, I code up a defun that is not necessary working but then I can test each of the inner sexps interactively.
(defun product-total-page (&optional (request *request*)) (let ((p (session-value :selected-product)_1) (q (session-value :quantity)_2)) (with-html (:html (:body (:table (:tr (:td "Product") (:td (str p))) (:tr (:td "Quantity") (:td (str q))) (:tr (:td "Total") (:td (str (* q (product-price p))))))))))_3)
What I do is that I place the cursor at the end of a sexp (marked by _) that I want to test and send it to slime with c-x c-e
;; c-x c-e with cursor at _1 ;; emacs message buffer shows "T-Shirt XXL"
;; c-x c-e with cursor at _2 10
;; c-x c-e with cursor at _3 "<html><body>....</body></html>"
;; eval the following in the REPL
(product-total-page)
=> "<html><body>....</body></html>"
The above example has no errors , but you can imagine that it _evolves_ from a broken state to the working code.
Now if you require me to wrap every sexp with your (with-leaked-variables) macro before I can test them, I think I'll just give up and go back to perl or python or whatever (they have libraries covering everything you want to write BTW ;-)
Regards, -- Mac
(PS: Have you seen Marco Baringer's slime video?)
Scribit Mac Chan dies 03/05/2007 hora 21:02:
I find it strange that you don't follow what my intention was because I thought that's how everyone else write lisp code in a bottom up style
I really do both styles, bottom-up and top-down, so I really didn't understand what you wanted to do.
Now if you require me to wrap every sexp with your (with-leaked-variables) macro before I can test them, I think I'll just give up
Don't, your wish is just a macro away:
http://arcanes.fr.eu.org/~pierre/2007/05/cl-leak/?rev/dd3cb6ee7410
In your case, you'd still do (leak-variables *leak* ...) in a handler to capture the various variables you need, and it's not restricted to dynamic variables related to Hunchentoot. Then you'd evaluate (setf-leaked-variables *leak*) in the REPL and you're in the exact situation *debug-mode* was bringing you in.
Except it's now more flexible, and there's a conditional branching and a cache miss that won't go in Hunchentoot's critical path.
Quickly, Pierre
On 5/4/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
Except it's now more flexible, and there's a conditional branching and a cache miss that won't go in Hunchentoot's critical path.
Not that I insist we put something something redundant into Hunchentoot's critical path, as stated in my reply to Edi, I found a workaround and I'm happy that I can go back to the way that I used to work with tbnl. No complaints here.
But I find it funny that you repeatedly say that there _will_ be a cache miss using the debug-value macro. How do you know? Maybe you meant _potential_? I think it's incredibly painful to consider that scenario when one is writing lisp code. Even with years of c programming I have only come to see one or two examples of loop unrolling at work.
Have you done any loop unrolling in lisp? (maybe lisp compiler is smart enough to do that...) Do you declare all the types in your code? I think most people come to lisp for fast prototyping and implementation correctness. Lisp by default is fast enough for most things.
In your case, you'd still do (leak-variables *leak* ...) in a handler to capture the various variables you need, and it's not restricted to dynamic variables related to Hunchentoot. Then you'd evaluate (setf-leaked-variables *leak*) in the REPL and you're in the exact situation *debug-mode* was bringing you in.
Don't, your wish is just a macro away:
No it isn't. You're creating more work for me. In my toy example if I were to add a few more products in the cart using the browser, I'll need to evaluate (setf-leaked-variables *leak*) to have it reflect in *session*.
In fact I need to eval (setf-leaked-variables *leak*) everytime I change something with the browser. That would take away all of fun, wouldn't you agree?
-- Mac
I agree with Mac. For me it's a question of "how much work I can do without having to open the browser to test the functionality". Maybe it's a bad design that I use, but I refer to the *session* variables not only from the final *dispatch-table* function but from some intermediary ones too. That means I can only test them by opening the web page. It would be great if I could build the whole functionality from slime and only check the final stage from the browser.
Andrew
On 5/4/07, Mac Chan emailmac@gmail.com wrote:
On 5/4/07, Pierre THIERRY nowhere.man@levallois.eu.org wrote:
Except it's now more flexible, and there's a conditional branching and a cache miss that won't go in Hunchentoot's critical path.
Not that I insist we put something something redundant into Hunchentoot's critical path, as stated in my reply to Edi, I found a workaround and I'm happy that I can go back to the way that I used to work with tbnl. No complaints here.
But I find it funny that you repeatedly say that there _will_ be a cache miss using the debug-value macro. How do you know? Maybe you meant _potential_? I think it's incredibly painful to consider that scenario when one is writing lisp code. Even with years of c programming I have only come to see one or two examples of loop unrolling at work.
Have you done any loop unrolling in lisp? (maybe lisp compiler is smart enough to do that...) Do you declare all the types in your code? I think most people come to lisp for fast prototyping and implementation correctness. Lisp by default is fast enough for most things.
In your case, you'd still do (leak-variables *leak* ...) in a handler to capture the various variables you need, and it's not restricted to dynamic variables related to Hunchentoot. Then you'd evaluate (setf-leaked-variables *leak*) in the REPL and you're in the exact situation *debug-mode* was bringing you in.
Don't, your wish is just a macro away:
No it isn't. You're creating more work for me. In my toy example if I were to add a few more products in the cart using the browser, I'll need to evaluate (setf-leaked-variables *leak*) to have it reflect in *session*.
In fact I need to eval (setf-leaked-variables *leak*) everytime I change something with the browser. That would take away all of fun, wouldn't you agree?
-- Mac _______________________________________________ tbnl-devel site list tbnl-devel@common-lisp.net http://common-lisp.net/mailman/listinfo/tbnl-devel
Scribit Mac Chan dies 04/05/2007 hora 11:11:
But I find it funny that you repeatedly say that there _will_ be a cache miss using the debug-value macro. How do you know? Maybe you meant _potential_?
Yeah, that was incorrect, that's only a possible cache miss.
I think it's incredibly painful to consider that scenario when one is writing lisp code.
Well, you typically only have to worry about cache misses in the critical path of some part of a system whose performance affect it as a whole, like IPC in a µ-kernel or request handling in a network server, it seems.
Have you done any loop unrolling in lisp?
Not really. I only studied loop unrolling as I was a C++ programmer, and C++ make it a pain, because you need black magic. Now that I begin to understand Lisp's macros, I barely begin to imagine I could do easy and efficient loop unrolling sometimes...
Do you declare all the types in your code? I think most people come to lisp for fast prototyping and implementation correctness. Lisp by default is fast enough for most things.
The critical path of my applications so far was always in underlying libraries, like Hunchentoot or Elephant, so I never had the need to profile and optimize my own code.
In fact I need to eval (setf-leaked-variables *leak*) everytime I change something with the browser. That would take away all of fun, wouldn't you agree?
No, because it happened quite some times that I have to debug some part of an application under stress, and then having the various debugging variables rebound by each request would have been unacceptable.
If some people find this *debug-mode* really useful, though, it shouldn't be that hard to write the switch not as a dynamic variable (used much as a global variable, in this case) but as a function that changes the various functions of Hunchentoot where the variables are bound. When in a non-debug mode, it would store the debugging alternatives, and in debug mode, it would store back the current ones...
Curiously, Pierre
I've been following the thread and thinking about why I'm not missing debug-value even though I do a lot of prototyping on the REPL.
It seems that I tend to write small functions and extract information from session variables as soon as possible, passing them on as functions arguments. Therefore, it is always quite natural for me to write 99% of the code without worrying about the particulars of what arrives from the browser.