Subject: [bug#34948] [PATCH 1/3] records: Allow thunked fields to refer to 'this-record'.

From: Ludovic Courtès <[email-masked]>
Date: Sat, 23 Mar 2019 16:18:11 +0100

Hi!

Ricardo Wurmus <[email-masked]> skribis:

> Ludovic Courtès <[email-masked]> writes:
>
>> * guix/records.scm (this-record): New syntax parameter.
>> (make-syntactic-constructor)[wrap-field-value]: When F is thunked,
>> return a one-argument lambda instead of a thunk, and parameterize
>> THIS-RECORD.
>
> So the value of the thunked field is no longer strictly a thunk?

Indeed, it’s now a one-argument procedure.  It doesn’t matter much
though because users never see this procedure.

> I’m having difficulties understanding how this works.  Why does the
> “thunked field” now require an argument (“x”)?

This argument is the record itself, then bound to ‘this-record’ in the
lexical scope of the field.

> We use the syntax parameter “this-record” to introduce a new binding
> with this name in the context of the “value” of the field.  The
> parameter value is … hard to make out.  How does the syntax-case macro
> in the following syntax-parameterize expression evaluate to the record
> itself?  Would #,x not be sufficient to refer to the argument of the
> field accessor?
>
>>           (define (wrap-field-value f value)
>>             (cond ((thunked-field? f)
>> -                  #`(lambda () #,value))
>> +                  #`(lambda (x)
>> +                      (syntax-parameterize ((this-record
>> +                                             (lambda (s)
>> +                                               (syntax-case s ()
>> +                                                 (id
>> +                                                  (identifier? #'id)
>> +                                                  #'x)))))

Here ‘x’ is the identifier of a variable that exists at run time.  So we
cannot write #,x because we’d be referring to a variable ‘x’ that exists
at macro-expansion time, and there’s no such variable here.

The ‘syntax-case’ here is just so that ‘this-record’ matches only when
used as an identifier, like this:

  (foo this-record)

… and does not match when used like this:

  (this-record)

or like that:

  (this-record x y z)

We could just as well make it (identifier-syntax #'x) though that’s
slightly less precise.

A macro expansion is worth a thousand words :-), so:

--8<---------------cut here---------------start------------->8---
scheme@(guix records)> (define-record-type* <foo> foo make-foo foo?
			 (bar foo-bar (default 42))
			 (baz foo-baz (thunked)))
scheme@(guix records)> ,optimize (foo-baz x)
$11 = (let ((x x))
  ((if (eq? (struct-vtable x) <foo>)
     (struct-ref x 1)
     (throw 'wrong-type-arg
            '%foo-baz-real
            "Wrong type argument: ~S"
            (list x)
            (list x)))
   x))
scheme@(guix records)> ,optimize (foo (baz (+ 77 (foo-bar this-record))))
$12 = (begin
  (if (eq? #{% <foo> abi-cookie}# 2292347072401235576)
    (if #f #f)
    (throw 'record-abi-mismatch-error
           'abi-check
           "~a: record ABI mismatch; recompilation needed"
           (list <foo>)
           '()))
  (let ((s (allocate-struct <foo> 2)))
    (struct-set! s 0 42)
    (struct-set!
      s
      1
      (lambda (x)
        (+ 77
           (if (eq? (struct-vtable x) <foo>)
             (struct-ref x 0)
             (throw 'wrong-type-arg
                    'foo-bar
                    "Wrong type argument: ~S"
                    (list x)
                    (list x))))))
    s))
--8<---------------cut here---------------end--------------->8---

I hope this clarifies things!

Ludo’.