Take the 2-minute tour ×
Theoretical Computer Science Stack Exchange is a question and answer site for theoretical computer scientists and researchers in related fields. It's 100% free, no registration required.

In the paper “Making a Fast Curry: Push/Enter vs. Eval/Apply for Higher-order Languages” by Simon Marlow and Simon Peyton Jones it is told that a PAP heap object may be created in the push/enter model (Figure 2. “The evaluation rules.” “Rules for push/enter.”). It is created “if there aren’t enough pending arguments on the stack.” I can't figure out an example of a type correct program which execution may lead to such a situation.

share|improve this question

2 Answers 2

Functions can be partially applied, so you can end up with a situation in which a function is called with "not enough" arguments. For example, consider the map functional:

(* map : ∀α,β. (α → β) → list α → list β *)
let rec map f xs = 
  match xs with 
  | []      -> []
  | x :: xs -> (f x) :: map f xs

This will take a function f as an argument, and then pass it one argument, once for each element of the list. Now, consider a two argument function foo:

(* val foo : int → int → int *)
let foo x y = 2*x + y 

Then, if you map foo over a list of integers:

(* val bar : list (int → int → int) *)
let bar = map foo [1; 2; 3]

You get a thunk (because this paper is about lazy evaluation). To force one of these thunks, you need to scrutinize the list to get its head, and then force the evaluation of the head by applying it:

 let baz = match bar with
           | f :: fs -> f 6 

Now, scrutinizing bar will get you f bound to THUNK (foo 1), and fs bound to map foo [2;3]. Then, to apply f 6, we first evaluate foo 1. This pushes 1 on the stack, and since foo is really a two-argument function, foo 1 will not evaluate further -- it will return a fresh p, bound to PAP(foo 1).

Now the result is the thunk THUNK(p 6) -- to drive evaluation further, you have to demand this thunk.

share|improve this answer
    
This example is not relevant. “(f x) :: map f xs” normalizing ==> “let fx = f x in fx :: map f xs”. “Let” creates a heap object and does not read or write stack at all. So the condition “if there aren’t enough pending arguments on the stack” is meaningless. And we only know that “f” has at least 1 argument. 1 argument is full application, 2 or 3 arguments is partial application. We don't know if this is a partial application. –  beroal Nov 29 '12 at 14:07
    
Besides, there is no “function call” in push/enter. There is only “enter”. –  beroal Nov 29 '12 at 14:10
    
Sorry, I forgot about laziness -- I added a small bit at the end to force evaluation, so that you can see when the partial application objects are constructed. –  Neel Krishnaswami Nov 29 '12 at 19:42
    
Actually, you are describing “eval/apply” because you are evaluating “f” in “f 6”. When using “push/enter”, “f 6” does: push 6, enter f = THUNK (foo 1), push 1, enter foo. No PAP. Still looking for an answer… (P.S. IMHO the “map” with its big definition is not important here.) –  beroal Nov 30 '12 at 12:43

Consider the following lazy functional program:

doArith :: Int -> Int
doArith n = if n < 0 then 1 + 2 else 3 - 4

A compiler can figure out that it doesn't need to build a thunk for 1 + 2 or 3 - 4 in the if expression. This is because that an if expression is strict in the branch position, since either 1 + 2 or 3 - 4 will surely be evaluated after evaluating n < 0. In this way, the compiler can generate better code while still preserving the lazy semantics.

However, the following program shows some problem:

getFunction :: Int -> Int -> Int
getFunction n = if n < 0 then (+) 1 else (-) 2

Here, the compiler will still generate strict code that enters either (+) or (-) with only one argument. But (+) or (-) require two arguments... So here comes the PAp object.

For further reading, I would suggest Simon Peyton Jones's implementing functional languages: a tutorial. It illustrates, step by step, three ways to implement lazy functional programming language.

share|improve this answer
    
“A compiler can figure out that it doesn't need to build a thunk for 1 + 2” In the push/enter model, a compiler does not build a thunk for 1 + 2. It pushes 1, 2, then enters (+). –  beroal Sep 24 '13 at 21:18

Your Answer

 
discard

By posting your answer, you agree to the privacy policy and terms of service.

Not the answer you're looking for? Browse other questions tagged or ask your own question.