2013-04-10 - Explicit polymorphism - Web Programming - Introduction to Continuations: Web Programming ======================================================================== >>> Explicit polymorphism [[[ PLAI Chapter 29 ]]] Consider the `length' definition that we had -- it is specific for `NumList's, so rename it to `lengthNum': {with-type {NumList ...} {rec {lengthNum : (NumList -> Num) {fun {l : NumList} : Num {cases l [{NumEmpty} 0] [{NumCons x r} {+ 1 {call lengthNum r}}]}}} {call lengthNum {NumCons 1 {NumCons 2 {NumCons 3 {NumEmpty}}}}}}} To simplify things, assume that types are previously defined, and that we have an even more Racket-like language where we simply write a `define' form: {define lengthNum {fun {l : NumList} : Num {cases l [{NumEmpty} 0] [{NumCons x r} {+ 1 {call lengthNum r}}]}}} What would happen if, for example, we want to take the length of a list of booleans? We won't be able to use the above code since we'd get a type error. Instead, we'd need a separate definition for the other kind of length: {define lengthBool {fun {l : BoolList} : Num {cases l [{BoolEmpty} 0] [{BoolCons x r} {+ 1 {call lengthBool r}}]}}} We've designed a statically typed language that is effective in catching a large number of errors, but it turns out that it's too restrictive -- we cannot implement a single generic `length' function. Given that our type system allows an infinite number of types, this is a major problem, since every new type that we'll want to use in a list requires writing a new definition for a length function that is specific to this type. One way to address the problem would be to somehow add a new `length' primitive function, with specific type rules to make it apply to all possible types. (Note that the same holds for the list type too -- we need a new type definition for each of these, so this solution implies a new primitive type that will do the same generic trick.) This is obviously a bad idea: there are other functions that will need the same treatment (`append', `reverse', `map', `fold', etc), and there are other types with similar problems (any new container type). A good language should allow writing such a length function inside the language, rather than changing the language for every new addition. Going back to the code, a good question to ask is what is it exactly that is different between the two `length' functions? The answer is that there's very little that is different. To see this, we can take the code and replace all occurrences of `Num' or `Bool' by some `???'. Even better -- this is actually abstracting over the type, so we can use a familiar type variable, τ: {define length〈τ〉 {fun {l : 〈τ〉List} : Num {cases l [{〈τ〉Empty} 0] [{〈τ〉Cons x r} {+ 1 {call length〈τ〉 r}}]}}} This is a kind of a very low-level "abstraction" -- we replace parts of the text -- parts of identifiers -- with a kind of a syntactic meta variable. But the nature of this abstraction is something that should look familiar -- it's abstracting over the code, so it's similar to a macro. It's not really a macro in the usual sense -- making it a real macro involves answering questions like what does `length' evaluate to (in the macro system that we've seen, a macro is not something that is a value in itself), and how can we use these macros in the `cases' patterns. But still, the similarity should provide a good intuition about what goes on -- and in particular the basic fact is the same: this *is* an abstraction that happens at the syntax level, since typechecking is something that happens at that level. To make things more manageable, we'll want to avoid the abstraction over parts of identifiers, so we'll move all of the meta type variables, and make them into arguments, using "〈...〉" brackets to stand for "meta level applications": {define length〈τ〉 {fun {l : List〈τ〉} : Num {cases l [{Empty〈τ〉} 0] [{Cons〈τ〉 x r} {+ 1 {call length〈τ〉 r}}]}}} Now, the first "〈τ〉" is actually a kind of an input to `length', it's a binding that has the other `τ's in its scope. So we need to have the syntax reflect this somehow -- and since `fun' is the way that we write such abstractions, it seems like a good choice: {define length {fun {τ} {fun {l : List〈τ〉} : Num {cases l [{Empty〈τ〉} 0] [{Cons〈τ〉 x r} {+ 1 {call length〈τ〉 r}}]}}}} But this is very confused and completely broken. The new abstraction is not something that is implemented as a function -- otherwise we'll need to somehow represent type values within our type system. (Trying that has some deep problems -- for example, if we have such type values, then it needs to have a type too; and if we add some `Type' for this, then `Type' itself should be a value -- one that has *itself* as its type!) So instead of `fun', we need a new kind of a syntactic, type-level abstraction. This is something that is acts as a function that gets used by the type checker. The common way to write such functions is with a capital `lambda' -- `Λ'. Since we already use Greek letters for things that are related to types, we'll use that as is (again, with "〈〉"s), instead of a confusing capitalized `Lambda' (or a similarly confusing `Fun'): {define length 〈Λ 〈τ〉 {fun {l : List〈τ〉} : Num {cases l [{Empty〈τ〉} 0] [{Cons〈τ〉 x r} {+ 1 {call length〈τ〉 r}}]}}〉} and to use this `length' we'll need to instantiate it with a specific type: {+ {call length〈Num〉 {list 1 2}} {call length〈Bool〉 {list #t #f}}} Note that we have several kinds of meta-applications, with slightly different intentions: * length〈τ〉 is the recursive call, which needs to keep using the same type that initiated the `length' call. It makes sense to have it there, since `length' is itself a type abstraction. * List〈τ〉 is using `List' as if it's also this kind of an abstraction, except that instead of abstracting over some generic code, it abstracts over a generic type. This makes sense too: it naturally leads to a generic definition of `List' that works for all types since it is also an abstraction. * Finally there are `Empty〈τ〉' and `Cons〈τ〉' that are used for patterns. This might not be necessary, since they are expected to be variants of the `List〈τ〉' type. But if we were doing this without pattern matching (for example, see the book) then we'd need `null?' and `rest' functions. In that case, the meta application would make sense -- `null?〈τ〉' and `rest〈τ〉' are the τ-specific versions of these functions which we get with this meta-application, in the same way that using `length' needs an explicit type. Actually, the last item points at one way in which the above sample calls: {+ {call length〈Num〉 {list 1 2}} {call length〈Bool〉 {list #t #f}}} are broken -- we should also have a type argument for `list': {+ {call length〈Num〉 {list〈Num〉 1 2}} {call length〈Bool〉 {list〈Bool〉 #t #f}}} or, given that we're in the limited picky language: {+ {call length〈Num〉 {cons〈Num〉 1 {cons〈Num〉 2 null〈Num〉}}} {call length〈Bool〉 {cons〈Bool〉 #t {cons〈Bool〉 #f null〈Bool〉}}}} Such a language is called "parametrically polymorphic with explicit type parameters" -- it's "polymorphic" since it applies to any type, and it's "explicit" since we have to specify the types in all places. ======================================================================== >> Polymorphism in the type description language Given our definition for `length', the type of `length〈Num〉' is obvious: length〈Num〉 : List〈Num〉 -> Num but what would be the type of `length' by itself? If it was a function (which was a broken idea we've seen), then we would write: length : τ -> (List〈τ〉 -> Num) But this is broken in the same way: the first arrow is fundamentally different than the second -- one is used for a `Λ', and the other for a `fun'. In fact, the arrows are even more different, because the two `τ's are very different: the first one *binds* the second. So the first arrow is bogus -- instead of an arrow we need some way to say that this is a type that "for any τ" is "List〈τ〉 -> Num". The common way to write that is something very familiar: length : ∀τ. List〈τ〉 -> Num Finally, `τ' is usually used as a meta type variable; for these types the convention is to use the first few Greek letters, so we get: length : ∀α. List〈α〉 -> Num And some more examples: filter : ∀α. (α->Bool) × List〈α〉 -> List〈α〉 map : ∀α,β. (α->β) × List〈α〉 -> List〈β〉 where `×' stands for multiple arguments (which isn't mentioned explicitly in Typed Racket). ======================================================================== >> Type judgments for explicit polymorphism and execution Given our notation for polymorphic functions, it looks like we're introducing a runtime overhead. For example, our `length' definition: {define length 〈Λ 〈α〉 {fun {l : List〈α〉} : Num {cases l [{Empty〈α〉} 0] [{Cons〈α〉 x r} {+ 1 {call length〈α〉 r}}]}}〉} looks like it now requires another curried call for each iteration through the list. This would be bad for two reasons: first, one of the main goals of static type checking is to *avoid* runtime work, so adding work is highly undesirable. An even bigger problem is that types are fundamentally a syntactic thing -- they should not exist at runtime, so we don't want to perform these type applications at runtime simply because we don't want types to exist at runtime. If you think about it, then every traditional compiler that typechecks code does so while compiling, not when the resulting compiled program runs. (A recent exception in various languages are "dynamic" types that are used in a way that is similar to plain (untyped) Racket.) This means that we want to eliminate these applications in the typechecker. Even better: instead of complicating the typechecker, we can begin by applying all of the type meta-application, and get a result that does not have any such applications or any type variables left -- then use the simple typechecker on the result. This process is called "type elaboration". As usual, there are two new formal rules for dealing with these abstractions -- one for type abstractions and another for type applications. Starting from the latter: Γ ⊢ E : ∀α.τ ——————————————————— Γ ⊢ E〈τ₂〉 : τ[τ₂/α] which means that when we encounter a type application E〈τ₂〉 where E has a polymorphic type ∀α.τ, then we substitute the type variable α with the input type τ₂. Note that this means that conceptually, the typechecker is creating all of the different (monomorphic) `length' versions, but we don't need all of them for execution -- having checked the types, we can have a single `length' function which would be similar to the function that Racket uses. To see how this works, consider our length use, which has a type of `∀α. List〈α〉 -> Num'. We get the following proof that ends in the exact type of `length' (remember that when you prove you climb up): Γ ⊢ length : ∀α. List〈α〉 -> Num —————————————————————————————————————————————— Γ ⊢ length〈Bool〉 : (List〈α〉 -> Num)[Bool/α] —————————————————————————————————————————————— Γ ⊢ length〈Bool〉 : List〈Bool〉 -> Num [...] —————————————————————————————————————————————— Γ ⊢ {call length〈Bool〉 {cons〈Bool〉 ...}} : Num The second rule for type abstractions is: Γ[α] ⊢ E : τ ——————————————————— Γ ⊢ 〈Λ〈α〉 E〉 : ∀α.τ This rule means that to typecheck a type abstraction, we need to check the body while binding the type variable α -- but it's not bound to some specific type. Instead, it's left unspecified (or non-deterministic) -- and typechecking is expected to succeed without requiring an actual type. If some specific type is actually required, then typechecking should fail. The intuition behind this is that a polymorphic function can be one only if it doesn't need some specific type -- for example, {fun {x} {- {+ x 1} 1}} is an identity function, but it's an identity that requires the input to be a number, and therefore it cannot have a polymorphic ∀α.α type like {fun {x} x}. Another example is our `length' function -- the actual type that the list holds better not matter, or our `length' function is not really polymorphic. This makes sense: to typecheck the function, this rule means that we need to typecheck the body, with α being some unknown type that cannot be used. One thing that we need to be careful when applying any kind of abstraction (and the first rule does just that for a very simple lambda-calculus-like language) is infinite loops. But in the case of our type language, it turns out that this lambda-calculus that gets used at the meta-level is one of the strongly normalizing kinds, therefore no infinite loops happen. Intuitively, this means that we should be able to do this elaboration in just one pass over the code. Furthermore, there are no side-effects, therefore we can safely cache the results of applying type abstraction to speed things up. In the case of `length', using it on a list of Num will lead to one such application, but when we later get to the recursive call we can reuse the (cached) first result. ======================================================================== >> Explicit polymorphism conclusions Quoted directly from the book: Explicit polymorphism seems extremely unwieldy: why would anyone want to program with it? There are two possible reasons. The first is that it's the only mechanism that the language designer gives for introducing parameterized types, which aid in code reuse. The second is that the language includes some additional machinery so you don't have to write all the types every time. In fact, C++ introduces a little of both (though much more of the former), so programmers are, in effect, manually programming with explicit polymorphism virtually every time they use the STL (Standard Template Library). Similarly, the Java 1.5 and C# languages support explicit polymorphism. But we can possibly also do better than foist this notational overhead on the programmer. ======================================================================== >>> Web Programming [[[ PLAI Chapter 15 ]]] Consider web programming as a problem that arises frequently. The HTTP protocol is *stateless*: each HTTP query can be thought of as running a program (or a function), getting a result, then killing it. This makes interactive applications hard to write. For example, consider this behavior: * You go on a flight reservation website, and look at flights to Paris or London for a vacation. * You get a list of options, and choose one for Paris and one for London, control-click the first and then the second to open them in new tabs. * You look at the descriptions and decide that you like the first one best, so you click the button to buy the ticket. * A month later you go on your plane, and when you land you realize that you're in the wrong country -- the ticket you payed for was the second one after all... Obviously there is some fundamental problem here -- especially given that this problem plagued many websites early on (and these days these kind of problems are still there, except that people are much more aware of it, and are much more prepared to deal with it). In an attempt to clarify what it is exactly that went wrong, we might require that each interaction will result in something that is deterministically based on what the browser window shows when the interaction is made -- but even that is not always true. Consider the same scenario except with a bookstore and an "add to my cart" button. In this case you *want* to be able to add one item to the cart in the first window, then switch to the second window and click "add" there too: in this case you want to end up with a cart that has both items. The basic problem here is HTTP's statelessness, something that both web servers and web browsers use extensively. Browsers give you navigation buttons and sometimes will not even communicate with the web server when you use them (instead, they'll show you cached pages), they give you the ability to open multiple windows or tabs from the current one, and they allow you to "clone" the current tab. If you view each set of HTTP queries as a session -- this means that web browsers allow you to go back and forth in time, explore multiple futures in parallel, and clone your current world. These are features that the HTTP protocol intentionally allows by being stateless, and that people have learned to use effectively. A stateful protocol (like ssh, or ftp) will run in a single process (or a program, or a function) that is interacting with you directly, and this process dies only when you disconnect. A big advantage of stateful protocols is their ability to be very interactive and rely on state (eg, an editor updates a character on the screen, relying on the rest of it showing the same text); but stateless protocols can scale up better, and deal with a more hectic kind of interaction (eg, open a page on an online store, keep it open and buy the item a month later; or any of the above "time manipulation" devices). [Side-note: Some people think that Ajax is the answer to all of these problems. In reality, Ajax is layered on top of (asynchronous) web queries, so in fact it is the exact same situation. You do have an option of creating an application that works completely on the client side, but that wouldn't be as attractive -- and even if you do so, you're still working inside a browser that can play the same time tricks.] ======================================================================== >> Basic web programming [[[ PLAI Chapter 16 ]]] Obviously, writing programs to run on a web server is a profitable activity, and therefore highly desirable. But when we do so, we need to somehow cope with the web's statelessness. To see the implications from a PL point of view we'll use a small "toy" example that demonstrates the basic issues -- an "addition" service: - Server sends a page asking for a number, - User types a number and hits enter, - Server sends a second page asking for another number, - User types a second number and hits enter, - Server sends a page showing the sum of the two numbers. [Such a small applications are not realistic, of course: you can obviously ask for both numbers on the same page. They are very effective, though, in minimizing the general problem of web interaction to a more comprehensible small core problem.] Starting from just that, consider how you'd *want* to write the code for such a service. (If you have experience writing web apps, then try to forget it for now.) (web-display (+ (web-read "First number") (web-read "Second number"))) But this is never going to work. The interaction is limited to presenting the user with some data and that's all -- you cannot do any kind of interactive querying. We therefore must turn this server function into three separate functions: one that shows the prompt for the first number, one that gets the value entered and shows the second prompt, and a third that shows the results page. Assuming a generic "query argument" that represents the browser request, and a return value that represents a page for the browser to render, we have: (define (f1 query) ... show the first question ...) (define (f2 query) ... extract the number from the query ... ... show the second question ...) (define (f3 query) ... extract the number from the query ... ... show the sum ...) Note that `f2' receives the first number directly, but `f3' doesn't. Yet, it is obviously needed to show the sum. A typical hack to get around this is to use a "hidden field" in the HTML form that `f2' generates, where that field holds the second result. To make things more concrete, we'll use some imaginary web API functions: (define (f1 query) (web-read "First number" 'n1 "f2")) (define (f2 query) (let ([n1 (get-field query 'n1)]) (with-hidden-field 'n1 n1 (web-read "Second number" 'n2 "f3")))) (define (f3 query) (web-display "Your two numbers sum up to: " (+ (get-field query 'n1) (get-field query 'n2)))) Which would (supposedly) result in something like the following html forms when the user enters 1 and 2: http://.../f1
http://.../f2 http://.../f3 This is often a bad solution: it gets very difficult to manage with real services where the "state" of the server consists of much more than just a single number -- and it might even include values that are not expressible as part of the form (for example an open database connection or a running process). Worse, the state is all saved in the client browser -- if it dies, then the interaction is gone. (Imagine doing your taxes, and praying that the browser won't crash a third time.) Another common approach is to store the state information on the server, and use a small handle (eg, in a cookie) to identify the state, then each function can use the cookie to retrieve the current state of the service -- but this is exactly how we get to the above bugs. It will fail with any of the mentioned time-manipulation features. ======================================================================== >>> Introduction to Continuations: Web Programming To try and get a better solution, we'll re-start with the original expression: (web-display (+ (web-read "First number") (web-read "Second number"))) and assuming that `web-read' works, we need to begin with executing the first read: (web-read "First number") Then we need to take that result and plug it into an expression that will read the second number and sum the results -- that's the same as the first expression, except that instead of the first `web-read' we use a "hole": (web-display (+ <*> (web-read "Second number"))) where `<*>' marks the point where we need to plug the result of the first question into. A better way to explain this hole is to make it a function argument: (lambda (<*>) (web-display (+ <*> (web-read "Second number")))) Actually, we can split the second and third steps in the same way. First see how the first step is combined with the second "consumer" function: ((lambda (<*>) (web-display (+ <*> (web-read "Second number")))) (web-read "First number")) And now we can continue doing this and split the body of the consumer: (web-display (+ <*> (web-read "Second number"))) into a "reader" and the rest of the computation (using a new hole): (web-read "Second number") ; reader part (web-display (+ <*> <*2>)) ; rest of comp Doing all of this gives us: ((lambda (<*1>) ((lambda (<*2>) (web-display (+ <*1> <*2>))) (web-read "Second number"))) (web-read "First number")) And now we can proceed to the main trick. Conceptually, we'd like to think about `web-read' as something that is implemented in a simple way: (define (web-read prompt) (printf "~a: " prompt) (read-number)) To accommodate the above "hole functions", we change it by adding an argument for the consumer function that we need to pass the result to, and call it `web-read/k': (define (web-read/k prompt k) (printf "~a: " prompt) (k (read-number))) [In this version of `web-read' the `k' argument is the *continuation* of the computation. (`k' is a common name for a continuation argument.)] This is not too different from the previous version -- the only difference is that we make the function take a consumer function as an input, and hand it what we read instead of just returning it. Using it makes things a little easier, since we pass the hole function which gets called automatically, instead of combining things ourselves: (web-read/k "First number" (lambda (<*1>) (web-read/k "Second number" (lambda (<*2>) (web-display (+ <*1> <*2>)))))) You might notice that this looks too complicated; we could get exactly the same result with: (web-display (+ (web-read/k "First number" (lambda (<*>) <*>)) (web-read/k "Second number" (lambda (<*>) <*>)))) but then there's not much point to having `web-read/k' at all... So why have it? Because using it, we can make each application of `web-read/k' be one of these server functions, where the computation dies after the interaction. The only thing that `web-read/k' needs to worry about is storing the receiver function in a hash table somehow so it can be retrieved when the user is done with the interaction. (This means that the "submit" button will somehow encode a reference to the hash table that can make the next service call retrieve the stored function.) The `web-read/k' format is fitting for such an interaction, because at each step just the reading is happening -- everything else is put inside the consumer function. ======================================================================== >> Simulating web reading We can actually try all of this in plain Racket by simulating web interactions. This is useful to look at the core problem while avoiding the whole web mess that is uninteresting for this discussion. The main feature that we need to emulate is statelessness -- and we can achieve that using `error' to guarantee that each interaction is properly killed. We will do this in `web-display' which simulates sending the results to the client and therefore terminating the program on the server. More importantly, we need to do it in `web-read/k' -- in this case, the termination happens after we save the requested receiver in a `what-next' box. Instead of storing just the receiver there, we will store a function that does the prompting and the reading and then invoke the receiver. `what-next' is therefore bound to a box that holds a no-argument function that does the work of resuming the computation, and when there is nothing next, it is bound to a `nothing-to-do' function that throws an appropriate error. `resume' simply invokes the current `what-next'. (define (nothing-to-do) (error "There is no computation in progress now.")) (define what-next (box nothing-to-do)) (define (web-display n) (set-box! what-next nothing-to-do) (error 'web-output "~s" n)) (define (web-read/k prompt k) ;; note that a more accurate web-like behavior would print the ;; prompt before aborting, but this doesn't change anything ;; important, so we go with a more convenient time to show it (set-box! what-next (lambda () (printf "~a: " prompt) (k (read)))) (error 'web-read/k "enter (resume) to continue")) (define (resume) ;; to avoid mistakes, we clear out `what-next' before invoking it (let ([next (unbox what-next)]) (set-box! what-next nothing-to-do) (next))) We can now try out our code for the addition server, but switch to plain argument names instead of those `<*>'s: (web-read/k "First number" (lambda (n1) (web-read/k "Second number" (lambda (n2) (web-display (+ n1 n2)))))) and see how everything works. You can also try now the bogus expression that we mentioned: (web-display (+ (web-read/k "First number" (lambda (n) n)) (web-read/k "Second number" (lambda (n) n)))) and see how it breaks. ======================================================================== If we now sit back for a minute and checkout what we actually did at a higher level, it should ring a bell. We've taken a simple compound expression and "linearized" it as a sequence of an input operation and a continuation receiver for its result. This is essentially the same thing that we did to deal with IO in the lazy language -- and the similarity is not a coincidence. The problem that we faced there was very different (representing IO as values that describe it), but it originates from a similar situation -- some computation goes on (in whatever way the lazy language decides to evaluate it), and when we have a need to read something we must return a description of this read that contains "the rest of the computation" to the eager part of the interpreter that executes the IO. Once that part has the user input, it sends it to this computation remainder, which can return another read request, and so on. Based on this intuition, we can guess that this can work for any piece of code, and that we can even come up with a nicer "flat" syntax for it. For example, here is a simple macro that flattens a sequence of reads and a final display: (define-syntax web-code (syntax-rules (read display) [(_ (read n prompt) more ...) (web-read/k prompt (lambda (n) (web-code more ...)))] [(_ (display last)) (web-display last)])) and using it: (web-code (read x "First number") (read y "Second number") (display (+ x y))) However, we'll avoid such cuteness to make the transformation more explicit for the sake of the discussion. Eventually, we'll see how things can become even better than that in Racket: we can get to write plain-looking Racket expressions and avoid even the need for an imperative form for the code. In fact, it's easy to write this addition server using Racket's web server framework, and the core of the code looks very simple: (define (start initial-request) (page "The sum is: " (+ (web-read "First number") (web-read "Second number")))) There is not much more than that -- it has two utilities, `page' creates a well-formed web page, and `web-read' performs the reading. The main piece of magic there is in `send/suspend' which makes the web server capture the computation's continuation and store it in a hash table, to be retrieved when the user visits the given URL. Here's the full code: ---------------------------------------------------------------------- #lang web-server/insta (define (page . body) (response/xexpr `(html (body ,@(map (lambda (x) (if (number? x) (format "~a" x) x)) body))))) (define (web-read prompt) ((compose string->number (curry extract-binding/single 'n) request-bindings send/suspend) (lambda (k) (page `(form ([action ,k]) ,prompt ": " (input ([type "text"] [name "n"]))))))) (define (start initial-request) (page "The sum is: " (+ (web-read "First number") (web-read "Second number")))) ---------------------------------------------------------------------- ========================================================================