Arc Forumnew | comments | leaders | submitlogin
2 points by akkartik 3281 days ago | link | parent

Why 'defun factorial () n' and not 'defun factorial (n)'?


2 points by rocketnia 3280 days ago | link

I might agree with you there, but I'll take it as a question rather than a suggestion. :-p

The "n" is the function's argument, and () is the list of variables in the stack frame's environment.

In most Scheme-like languages, a visualization of the stack would be several levels of function calls. Every expression to the left would have been evaluated already, and every expression to the right would still be waiting. For instance:

  Current return value: 3
  
  Remaining stack (starting with the freshest activation):
  (plus _ #suspended:(negate (one (nil))))
  (factorial _)
  (times 3 _)
  (times 4 _)
  (times 5 _)
Staccato and Tenerezza have a more constrained model of the stack. In particular, the stack can never contain suspended code. As such, the program never quite lands in the above state, but I'll show the states before and after it:

  Current return value: 3
  Remaining stack:
  (factorial)
  (times 4)
  (times 5)
  
  Current return value: (nil)
  Remaining stack:
  (one)
  (negate)
  (plus 3)
  (factorial)
  (times 3)
  (times 4)
  (times 5)
This makes stack frames very simple. They're just tagged collections of values. This meshes into the rest of the language: To call a function is simply to push it onto the stack and to proceed with computing its argument. The (fn ...) syntax is just sugar for writing (def ...) and constructing a stack frame using variables from the surrounding lexical scope.

---

Incidentally, I had a bug in the abbreviated versions of factorial:

   (defun factorial () n
  -  (times n /call-new factorial () /plus n /negate/one))
  +  (times n /call-new factorial () /plus n /negate/one/nil))
These fancy macros can be called in two ways, either to construct a value or to call it as a function:

  (cons a b)    \ creates a (cons car cdr) first-class stack frame
  (cons a b c)  \ gets the result of (call (cons a b) c)
  (one)         \ creates a (one) first-class stack frame
  (one (nil))   \ gets the result of (call (one) (nil))
When I just wrote (one), I was generating a (one) stack frame, rather than actually retrieving the proper representation of 1. This could still work if the (one) stack frame was a proper representation of 1, but I intended the representation to be flexible.

-----

2 points by akkartik 3279 days ago | link

How would you represent multiple arguments? Or a variable number of them?

-----

2 points by rocketnia 3279 days ago | link

"How would you represent multiple arguments?"

Right now I'm juggling two ways to pass multiple arguments.

For globally defined functions, such as the ones in the examples, I construct a data structure like (plus term) which holds all but one of the "arguments," and then I call it with the final argument.

For higher-order functions, the call site is using a function that was constructed elsewhere, so it has to use some other method to cram in more than one argument. I think my favorite approach right now is to define a data structure specifically to carry these arguments. This way if the program is refactored and the arguments change, the new code can still be backwards compatible with old stack snapshots that mention the old data structure. All it takes is for the new code to have a conditional where it handles both the old tag and the new one.

The second calling convention could work for global functions too, and it might be a lot less awkward for documentation; when I describe a two-argument function called (plus term) or a four-argument function called (my-func a b c), it looks pretty weird. :) Unfortunately, it really commits us to having messy stack snapshots full of macro-generated steps:

  Current return value: (one-args)
  Remaining stack:
  (one)
  (gs-12301)    \ will construct (negate-args _)
  (negate)
  (gs-12302 3)  \ will construct (plus-args 3 _)
  (plus)
  (gs-12303)    \ will construct (factorial-args _)
  (factorial)
  (gs-12304 3)  \ will construct (times-args 3 _)
  (times)
  (gs-12304 4)  \ will construct (times-args 4 _)
  (times)
  (gs-12304 5)  \ will construct (times-args 5 _)
  (times)
Hmm, maybe this isn't much worse than before. Factorial is a funny example because I can put all the function calls in the last argument, which makes the stack look nice. If I put them all in the first argument instead, we'd see some macro-generated save points:

  (defun factorial () n
    (times (call-new factorial () /plus (negate/one/nil) n) n))

  Current return value: (nil)
  Remaining stack:
  (one)
  (negate)
  (gs-12305 3)  \ will call (plus _ #suspended:n)
  (factorial)
  (gs-12306 3)  \ will call (times _ #suspended:n)
  (gs-12306 4)  \ will call (times _ #suspended:n)
  (gs-12306 5)  \ will call (times _ #suspended:n)
Real code is likely to have a handful of save points per function, so the stack will usually look something like that.

Oh. Actually, if I passed arguments in the form of data structures, that bizarro-factorial would look a little nicer on the stack. You can actually tell that it's going to call (plus) and (times) even if you don't look up the definitions of gensyms:

  Current return value: (one-args)
  Remaining stack:
  (one)
  (gs-12301)    \ will construct (negate-args _)
  (negate)
  (gs-12302 3)  \ will construct (plus-args _ #suspended:n)
  (plus)
  (gs-12303)    \ will construct (factorial-args _)
  (factorial)
  (gs-12304 3)  \ will construct (times-args _ #suspended:n)
  (times)
  (gs-12304 4)  \ will construct (times-args _ #suspended:n)
  (times)
  (gs-12304 5)  \ will construct (times-args _ #suspended:n)
  (times)
Okay, I might settle on using this calling convention for everything in this convenience layer. :) Thanks for prompting me to think about this.

I guess I'll probably use a slightly tweaked set of macros in future examples, taking this calling convention into account.

---

"Or a variable number of them?"

One option is to pass a cons list. Assuming there's a (list ...) macro, it's easy to call (foo ...) with a list:

  (foo/list ...)

-----

1 point by akkartik 3278 days ago | link

Oh so there actually is a constraint on the number of arguments you can pass in! Interesting.

-----