Message ID | 20190322172719.11199-1-ludo@gnu.org |
---|---|
State | Accepted |
Commit | abd4d6b33dba4de228e90ad15a8efb456fcf7b6e |
Headers | show |
Series | Turn 'essential-services' into an <operating-system> field | expand |
Context | Check | Description |
---|---|---|
cbaines/applying patch | success | Successfully applied |
cbaines/applying patch | success | Successfully applied |
cbaines/applying patch | success | Successfully applied |
cbaines/applying patch | success | Successfully applied |
cbaines/applying patch | success | Successfully applied |
cbaines/applying patch | success | Successfully applied |
Ludovic Courtès <ludo@gnu.org> 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? I’m having difficulties understanding how this works. Why does the “thunked field” now require an argument (“x”)? 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))))) -- Ricardo
Hi! Ricardo Wurmus <rekado@elephly.net> skribis: > Ludovic Courtès <ludo@gnu.org> 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’.
I should mention that there are other craaaazzy applications of this! For example, the ‘self-native-input?’ field of <package> becomes useless, because now you can write: (package ;; … (native-inputs ;; Add self as a native input when cross-compiling. `(,@(if (%current-target-system) `(("this" ,this-record)) '()) ;; … ))) I think there are other cases in package definitions where this can be useful, possibly things like the ‘make-lua-*’ procedures that we have. Ludo’.
Hello! I’ve extended this a bit with these commits: d8bead6c5d system: Define 'this-operating-system'. adb6462c4c packages: Define 'this-package' and 'this-origin'. d2be7e3c4b records: Support custom 'this' identifiers. Now you can refer to ‘this-package’ and it will refer to the closest package in scope. The good thing is that you can refer to ‘this-package’ from within, say, an <origin> field, and it will DTRT. That also means you could have things such as: (define-record-type* <origin> ;; … (file-name origin-file-name (thunked) (default (string-append (package-name this-package) "-source")))) … which is pretty fun when you think about it, since it allows you to implicitly refer to the lexically surrounding package. That reminds me of Scala’s “implicit parameters”: https://docs.scala-lang.org/tour/implicit-parameters.html Ludo’.
Ludovic Courtès <ludo@gnu.org> skribis: > I should mention that there are other craaaazzy applications of this! > > For example, the ‘self-native-input?’ field of <package> becomes > useless, because now you can write: > > (package > ;; … > (native-inputs > ;; Add self as a native input when cross-compiling. > `(,@(if (%current-target-system) > `(("this" ,this-record)) > '()) > ;; … > ))) Done in a7646bc5e17a829d23519d0b199a576fb1edbd04! Ludo'.
diff --git a/guix/records.scm b/guix/records.scm index 0649c90ea3..244b124098 100644 --- a/guix/records.scm +++ b/guix/records.scm @@ -25,6 +25,8 @@ #:use-module (ice-9 regex) #:use-module (ice-9 rdelim) #:export (define-record-type* + this-record + alist->record object->fields recutils->alist @@ -93,6 +95,17 @@ interface\" (ABI) for TYPE is equal to COOKIE." (() #t))))))) +(define-syntax-parameter this-record + (lambda (s) + "Return the record being defined. This macro may only be used in the +context of the definition of a thunked field." + (syntax-case s () + (id + (identifier? #'id) + (syntax-violation 'this-record + "cannot be used outside of a record instantiation" + #'id))))) + (define-syntax make-syntactic-constructor (syntax-rules () "Make the syntactic constructor NAME for TYPE, that calls CTOR, and @@ -148,7 +161,14 @@ of TYPE matches the expansion-time ABI." (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))))) + #,value))) ((delayed-field? f) #`(delay #,value)) (else value))) @@ -308,7 +328,7 @@ inherited." (with-syntax ((real-get (wrapped-field-accessor-name field))) #'(define-inlinable (get x) ;; The real value of that field is a thunk, so call it. - ((real-get x))))))) + ((real-get x) x)))))) (define (delayed-field-accessor-definition field) ;; Return the real accessor for FIELD, which is assumed to be a diff --git a/tests/records.scm b/tests/records.scm index d9469a78bd..45614093a0 100644 --- a/tests/records.scm +++ b/tests/records.scm @@ -170,6 +170,46 @@ (parameterize ((mark (cons 'a 'b))) (eq? (foo-bar y) (mark))))))) +(test-assert "define-record-type* & thunked & this-record" + (begin + (define-record-type* <foo> foo make-foo + foo? + (bar foo-bar) + (baz foo-baz (thunked))) + + (let ((x (foo (bar 40) + (baz (+ (foo-bar this-record) 2))))) + (and (= 40 (foo-bar x)) + (= 42 (foo-baz x)))))) + +(test-assert "define-record-type* & thunked & default & this-record" + (begin + (define-record-type* <foo> foo make-foo + foo? + (bar foo-bar) + (baz foo-baz (thunked) + (default (+ (foo-bar this-record) 2)))) + + (let ((x (foo (bar 40)))) + (and (= 40 (foo-bar x)) + (= 42 (foo-baz x)))))) + +(test-assert "define-record-type* & thunked & inherit & this-record" + (begin + (define-record-type* <foo> foo make-foo + foo? + (bar foo-bar) + (baz foo-baz (thunked) + (default (+ (foo-bar this-record) 2)))) + + (let* ((x (foo (bar 40))) + (y (foo (inherit x) (bar -2))) + (z (foo (inherit x) (baz -2)))) + (and (= -2 (foo-bar y)) + (= 0 (foo-baz y)) + (= 40 (foo-bar z)) + (= -2 (foo-baz z)))))) + (test-assert "define-record-type* & delayed" (begin (define-record-type* <foo> foo make-foo