⬆️ ⬇️

continuation and stateful web programming (Updated!)

The idea is not new at all. The idea is ancient.

However, most observables around web frameworks persistently ignore this idea.



It is to use continuations to magically transform RESTful (stateless) web applications into a more convenient and familiar stateful format.



In essence, the “session” was designed to store the execution status of the web application.

And its use is stupid as a data set - solely due to the lack of full-featured sequels in the language.



Continuation is the state of application execution at the language level.

In the scheme, it is supported as first-class objects — they can be returned from functions, passed as function parameters, and finally called as functions.

When calling a continuation, the application returns to the place where the continuation was created, and continues to run from there.

And this can be done more than once.

In a way - these are exceptions, but acting in reverse.

An exception occurs at some point in the program and passes control somewhere outside - where the handler is installed.

Continuation occurs at some point in the program and allows you to transfer control back to this point. The program in this place will take it as it was.

')

For example, in python, this is implemented by the yield statement.

The function containing yield is considered an “iterator generator”, its execution is terminated at the first operator, yield, and the function returns an object of type iterator to which you can use next () to call the next iteration, and send (values) to return the value to the transition point by yield .

An iterator call with the next () and send () methods returns the next, next, value.

Iterators can be used to iterate numbers (and it can be incredibly fun, if the numbers are, say, fibonacci, and there is an infinite number of them), you can do a tree walk, you can iterate everything .

Likewise, an iterator can generate not numbers or nodes of a tree, but web pages.



For example, functions for viewing resources and putting them in the shopping cart might look like this:

 browse_stuff(): #             criteria = yield(ask_criteria()) #     results = find_stuff(criteria) #     ,     -   yield (list_stuff(results)) #     raise StopIteration() def buy_stuff(continuation, item) if not user.authentificated: #     ,  ,  user = yield(login_form(user)) #   ,   ,    # ,  quantity = yield(ask_quantity()) # ,   delivery = yield(ask_delivery()) #   money = yield(ask_payment()) #   status = launch_order(item,quantity,delivery,money) #     "" yield(message("  .  .")) #  ,     (  browse_stuff) continuation.next() # ,     " " ? 


The parameter continuation is the state of the application (execution of another function) in the place where the transition to the link “buy crap” was made.



The main application in this situation looks like an iterator.

cont = browse_stuff_iter ()

The cont object is saved in a session, or somewhere else, and is restored every time a client accesses it.

Or, several objects are saved, and their identifiers are encoded in URLs, stuck in hidden form fields, so that when the “back” button is pressed, the user does not just see the previous page, but actually returns to the previous state of the application.



On a GET request, cont.next () is called.

On a POST request, cont.send is called (form data)

The results of these calls are displayed as pages.

This method allows you to generate a chain of forms as in buy_stuff ()

When clicking on a new link, this is regarded as an interrupt and the current state is passed to the handler: buy_stuff (cont, item)



Upd:

It is worth noting that neither in python, nor tembolee in pkhp, nor in Zhaveh, there is no full support for sequels, at the level of first-class objects.

Python's yield behaves very similarly and was used to illustrate an idea.

How it will work in reality is not entirely clear.



Full support for continuations is available in various exotic languages , but also in Ruby as well.

There are several servers with continuation support for the scheme, lisp, smalltalk, OCaml and JavaScript.

Somewhere in the comments a link to a simple PHP emulator was lost.




The future of the web is unequivocally behind the language, supporting full-fledged continuations!



Especially if we recall that in a language that supports continuations as first-class objects, no other constructions, by and large, are generally needed (including even data structuring, encapsulation and inheritance constructions) - they are all implemented through continuations (and as a special case - functional closure))



Well, as is usually the case, such thoughts have already been expressed in a more structured and consistent manner :)



List of unused literature:





mega-UPD: illustration of the scheme

I can not vouch for the correctness of the code. but the brackets are balanced :)

 ;;  server code
 (define (uri-> cont uri) "get from where the continuation by its URI")
 (define (cont-> uri cont) "saves a continuation somewhere and forms URIs for it)

 (define (insert-uri tmpl uri) "inserts a link into a template")
 (define (render-page tmpl args) "renders the page in html")

 (define (handle-request request) "causes a continuation, passing request to it"
   (if (eqv? (get-uri request) init-uri));  if the request is a starting URI
       (start);  cause start
       ((uri-> cont (get-uri request)) request)));  otherwise - continued

 (define (make-response cont template) "template: a page with links or a form.
   (render-page (insert-uri template (cont-> uri cont))))
 ;;  end of server code

 ;;  application code
 (define (quest room)
   (define (parse-request request) "parses the form data or chonit and returns the choice")
   (define (walk-on choice) "selects a new room for the current and choice")
   (define (get-page) "returns the html template for the current room")
   (define (response cc) (make-response cc (get-page)) "stupid parameter karring")
   (quest (walk-on (parse-request (call / cc response))))))

 ;;  initialization code
 (define init-uri "/ mytextquest")
 (define (start) (quest 'start-room))


What is going on here (should be happening):

0. quest is called with a parameter identifying the room.

0.5 the only call is the last, the most extreme parameter - the expression (call / cc (response))

1. call / cc (this is a special construct that) calls a function (response continuation)

2. that in turn calls (make-response continuation template)

3. make-response inserts into the template (for example, in the action field, or in the navigation links) a URI identifying this continuation.

4. page rendered

5. The user clicks on one of the links on the page or submits the form.

6. handle-request by clicking on the link continued

7. and calls it with the request parameter

8. execution continues from the point where call / cc stands. the request value is substituted as the result of the call / cc call

9. request is parsed and we get the logic of what the user there has poked

10. walk-on calculates which room the user is in now

11. quest recursively called with a parameter that identifies the next room.



If, besides the room, the karma of the user influences the choice of path the contents of the pockets. and the state of the surrounding ecology - all this is encapsulated in a 'room'.

If the user pressed 'back' and switched to the previous URI, the previous state is pulled out and it really falls into the previous room, all the affected animals come alive, and so on.

Source: https://habr.com/ru/post/50204/



All Articles