Hacker News new | past | comments | ask | show | jobs | submit login

This is just Algol-style "call by name".

https://en.wikipedia.org/wiki/Evaluation_strategy#Call_by_na...

Call-by-name in Lisp macros, to implement Knuth's "Man or boy test" program: https://rosettacode.org/wiki/Man_or_boy_test#TXR

As you can see, the make-cbn-val constructor packages up the argument as an object ("thunk") that contains zero-argument lambda for getting the value, and a one-argument setter.

Inside a call-by-name function, references to all the call-by-name arguments are converted according to the pattern X -> (cbn-val X), using a lexical macro.

(cbn-val ...) syntax is treated as a place thanks to the (defplace (cbn-val thunk) ...) definition.

In the following, the (inc a) applied on the call-by-name argument a turns into (set-cbn-val #:g0098 (succ (cbn-val #:g0098)))): get the value, find successor, store the value.

  4> (expand '(defun-cbn foo (a) (inc a)))
  (progn (defun #:g0091
           ())
    (defmacro foo args (list* 'cbn '#:g0091 args))
    (let ((#:g0095 (sys:get-fun-getter-setter '#:g0091)))
      (call (cdr #:g0095)
            (lambda (#:g0092)
              (block foo (let ((foo))
                           (let ((#:g0098 #:g0092))
                             (set-cbn-val #:g0098 (succ (cbn-val #:g0098))))
                           foo))))))
An empty function is defined, named by a gensym. Two steps later in the progn, this function binding is overwritten with the real function. A macro called foo is generated which provides the syntactic sugar for calling foo, automatically converting the arguments to thunks. The syntax (foo a b c) turns into (cbn #:g0091 a b c), which is another macro operator for doing call by name.

The actual function starts at (lambda (#g:0092) ...). The (let ((foo)) ...) part simulates another Algol feature: returning a value is done by assigning to the function name. Our function returns nil because it doesn't assign anything to foo.

The call (foo x) expands like this:

  5> (macroexpand '(foo x))
  (call (fun #:g0083)
        (make-cbn-val
          x))
Or fully:

  6> (expand '(foo x))
  (call (fun #:g0083)
        (make-struct 'cbn-thunk
                     (list 'get (lambda () x)
                           'set (lambda (#:g0099)
                                  (sys:setq x #:g0099)))))
The make-struct call is an expansion of the new macro operator; that comes from the object system:

  11> (macroexpand-1 '(make-cbn-val x))
  (new cbn-thunk
    get (lambda () x)
    set (lambda (#:g0104)
          (set x #:g0104)))
In action: watch x get incremented by foo:

  12> (defvar x 10)
  x
  13> (foo x)
  nil
  14> x
  11
Of course, this is objectionably hacky because foo isn't a function; we can't pass it around or anything.

The goal was only to solve the Rosetta Code "Man or boy" task, with a focus on actually emulating the language features to some degree of fidelity, so that the program could truly be a transliteration of Knuth's code into Lisp expressions, almost node for node.




Well I had to try this "Man or Boy" test. I do get -67 :-)

    fn A(k, &x1, &x2, &x3, &x4, &x5) {
            fn B() {
                    k = k - 1
                    return A(k, B(), *x1, *x2, *x3, *x4)
            }
            if k <= 0 {
                    return *x4 + *x5
            } {
                    return B()
            }
    }

    print A(10, 1, -1, -1, 1, 0)
Only a few languages can do this without some form of explicit wrappers in the top-level call to A.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: