From patchwork Mon Dec 11 20:16:40 2023 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Skyler Ferris X-Patchwork-Id: 57531 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 9DA4627BBE9; Mon, 11 Dec 2023 20:18:04 +0000 (GMT) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-2.9 required=5.0 tests=BAYES_00,FREEMAIL_FROM, MAILING_LIST_MULTI,SPF_HELO_PASS autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id D233B27BBE2 for ; Mon, 11 Dec 2023 20:18:01 +0000 (GMT) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1rCmis-0000MB-6F; Mon, 11 Dec 2023 15:17:50 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rCmiq-0000Lr-1B for guix-patches@gnu.org; Mon, 11 Dec 2023 15:17:48 -0500 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1rCmip-0000F4-Pj for guix-patches@gnu.org; Mon, 11 Dec 2023 15:17:47 -0500 Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1rCmj4-00019H-Ma for guix-patches@gnu.org; Mon, 11 Dec 2023 15:18:02 -0500 X-Loop: help-debbugs@gnu.org Subject: [bug#67786] [PATCH] doc: Add documentation for define-record-type* Resent-From: Skyler Ferris Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 11 Dec 2023 20:18:02 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: report 67786 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: patch To: 67786@debbugs.gnu.org Cc: Skyler Ferris X-Debbugs-Original-To: guix-patches@gnu.org Received: via spool by submit@debbugs.gnu.org id=B.17023258314339 (code B ref -1); Mon, 11 Dec 2023 20:18:02 +0000 Received: (at submit) by debbugs.gnu.org; 11 Dec 2023 20:17:11 +0000 Received: from localhost ([127.0.0.1]:54730 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rCmiE-00017u-OW for submit@debbugs.gnu.org; Mon, 11 Dec 2023 15:17:11 -0500 Received: from lists.gnu.org ([2001:470:142::17]:51852) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1rCmiC-00017f-Tj for submit@debbugs.gnu.org; Mon, 11 Dec 2023 15:17:09 -0500 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rCmhr-0000I4-OO for guix-patches@gnu.org; Mon, 11 Dec 2023 15:16:47 -0500 Received: from c-24-143-119-132.customer.broadstripe.net ([24.143.119.132] helo=localhost) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1rCmho-00009y-Tv for guix-patches@gnu.org; Mon, 11 Dec 2023 15:16:47 -0500 Received: from [127.0.0.1] (helo=localhost.localdomain) by localhost with esmtp (Exim 4.96.1) (envelope-from ) id 1rCmhm-0000Kt-2P; Mon, 11 Dec 2023 12:16:42 -0800 Date: Mon, 11 Dec 2023 12:16:40 -0800 Message-ID: <9bf2efa9c7aab1661fcf5180d1e536fc6dc0e9b3.1702324538.git.skyvine@protonmail.com> X-Mailer: git-send-email 2.41.0 MIME-Version: 1.0 Received-SPF: softfail client-ip=24.143.119.132; envelope-from=skyvine@protonmail.com; helo=localhost X-Spam_score_int: 72 X-Spam_score: 7.2 X-Spam_bar: +++++++ X-Spam_report: (7.2 / 5.0 requ) BAYES_00=-1.9, FREEMAIL_FROM=0.001, FSL_HELO_NON_FQDN_1=0.001, HELO_LOCALHOST=3.828, KHOP_HELO_FCRDNS=0.26, RCVD_IN_PBL=3.335, RDNS_DYNAMIC=0.982, SPF_SOFTFAIL=0.665, SPOOFED_FREEMAIL=0.001, T_SCC_BODY_TEXT_LINE=-0.01 autolearn=no autolearn_force=no X-Spam_action: reject X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Skyler Ferris X-ACL-Warn: , Skyler Ferris via Guix-patches X-Patchwork-Original-From: Skyler Ferris via Guix-patches via From: Skyler Ferris Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches * doc/guix.texi: Add sections describing the typical usage and API reference for define-record-type* Change-Id: I19e7220553d10652c794e6e0172b2c9ee961f54f --- doc/contributing.texi | 1 - doc/guix.texi | 274 ++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 274 insertions(+), 1 deletion(-) base-commit: 2b782f67266b42bb40015bd23ce2443be2f9b01f diff --git a/doc/contributing.texi b/doc/contributing.texi index 9e9b89782c..60fcf95b77 100644 --- a/doc/contributing.texi +++ b/doc/contributing.texi @@ -1311,7 +1311,6 @@ Data Types and Pattern Matching notably the fact that it is hard to read, error-prone, and a hindrance to proper type error reports. -@findex define-record-type* @findex match-record @cindex pattern matching Guix code should define appropriate data types (for instance, using diff --git a/doc/guix.texi b/doc/guix.texi index 1fd2e21608..e9d0fd1466 100644 --- a/doc/guix.texi +++ b/doc/guix.texi @@ -12561,6 +12561,280 @@ G-Expressions @code{(*approximate*)}, but this may change. @end deffn +@node Records in Guix +@section Records in Guix +Guix uses @code{define-record-type*} to define structures with a lispy format. +Packages, operating systems, etc are all defined with +@code{define-record-type*} facilities. If one was using this facility to +define preferences for a text editor, it might look like this: + +@lisp +;; The only valid emulation modes are the symbol 'emacs, the symbol 'vim, or +;; the boolean #f. As a convenience to the user, if they pass in a string +;; first convert it to a symbol and accept it if it is valid. +(define (sanitize-emulation-mode value) + (let ((symbolized-value (cond ((not value) #f) + ((string? value) (string->symbol value)) + (#t value)))) + (unless (or (not symbolized-value) + (eq? symbolized-value 'emacs) + (eq? symbolized-value 'vim)) + (throw 'bad-emulation-made + (format #f "Unrecognized emulation mode: ~s" value))) + symbolized-value)) + +(define-record-type* + editor-preferences make-editor-preferences + editor-preferences? this-editor-preferences + (background-color editor-preferences-background-color + (default "000000")) + (text-color editor-preferences-text-color + (default "FFFFFF")) + (emulation-mode editor-preferences-emulation-mode + (default #f) + (sanitize sanitize-emulation-mode))) +@end lisp + +A user could then define their preferences like this: + +@lisp +(define my-preferences + (editor-preferences + (background-color "222222") + (emulation-mode 'vim))) +@end lisp + +The value contained in @code{my-preferences} contains a custom +@code{background-color} and @code{emulation-mode}, but keeps the default +@code{text-color} (@code{"FFFFFF"}). If an invalid @code{emulation-mode} had +been specified, for example if the user passed in @code{"vi"} instead of +@code{"vim"}, @code{sanitize-emulation-mode} would immediately throw an error. + +The program can access values like this: + +@lisp +(editor-preferences-background-color my-preferences) +@result{} "222222" +(editor-preferences-text-color my-preferences) +@result{} "FFFFFF" +(editor-preferences-emulation-mode my-preferences) +@result{} 'vim +@end lisp + +There is no way to define setters (all instances are immutable). + +@node Record Inheritance +@subsection Record Inheritance +It is also possible to inherit from previously defined instances when creating +new ones. Continuing with the editor example, someone might want to base their +preferences on their friend's preferences but customize a value: + +@lisp +(define friends-preferences + (editor-preferences + (inherit my-preferences) + (emulation-mode 'emacs))) +@end lisp + +This keeps the same @code{background-color} and @code{text-color} that are +contained in @code{my-preferences} but changes the @code{emulation-mode} to +be @code{'emacs} instead of @code{'vim}. + +Sometimes it does not make sense for a field to be inherited. Suppose that the +@code{} type is updated to contain a username so that a +friendly greeting can be displayed when the program starts up: + +@lisp +;; Usernames must be strings. It would be strange to pass a username as a +;; symbol, so throw an error in case the user meant to pass in a variable's +;; value instead of a literal symbol. +(define (sanitize-username value) + (unless (string? value) + (throw 'bad-username + (format #f "Usernames must be strings! Got: ~s" value))) + value) + +(define (sanitize-emulation-mode value) + (let ((symbolized-value (cond ((not value) #f) + ((string? value) (string->symbol value)) + (#t value)))) + (unless (or (not symbolized-value) + (eq? symbolized-value 'emacs) + (eq? symbolized-value 'vim)) + (throw 'bad-emulation-made + (format #f "Unrecognized emulation mode: ~s" value))) + symbolized-value)) + +(define-record-type* + editor-preferences make-editor-preferences + editor-preferences? this-editor-preferences + (username editor-preferences-username + (innate) + (sanitize sanitize-username)) + (background-color editor-preferences-background-color + (default "000000")) + (text-color editor-preferences-text-color + (default "FFFFFF")) + (emulation-mode editor-preferences-emulation-mode + (default #f) + (sanitize sanitize-emulation-mode))) +@end lisp + +There are a couple of differences in the new @code{username} field compared to +the fields we looked at earlier. It is marked as @code{innate}, which means +that it will not be inherited. For example, consider what would happen if we +tried to define new instances like this: + +@lisp +(define my-preferences + (editor-preferences + (username "my-username") + (background-color "222222") + (emulation-mode 'vim))) + +(define friends-preferences + (editor-preferences + (inherit my-preferences) + (emulation-mode 'emacs))) +@end lisp + +While the @code{friends-preferences} instance still inherits the values for +@code{background-color} and @code{text-color}, it will not inherit the value +for @code{username}. Furthermore, as the @code{username} field does not define +a default value the attempted creation of @code{friends-preferences} will +actually throw an error. Instead, we could do this: + +@lisp +(define my-preferences + (editor-preferences + (username "my-username") + (background-color "222222") + (emulation-mode 'vim))) + +(define friends-preferences + (editor-preferences + (inherit my-preferences) + (username "friends-username") + (emulation-mode 'emacs))) +@end lisp + +@node @code{define-record-type*} Reference +@subsection @code{define-record-type*} Reference +@defmac define-record-type* name syntactic-constructor constructor predicate this-identifier fields ... + +Define a new record type and associated helpers. + +@table @var +@item name +A symbol used to name the type, as would normally be provided to a plain +@code{define-record-type} form. For example, @code{}. + +@item syntactic-constructor +A symbol that will be used to define the user-facing constructor. For example, +the symbol @code{package} is the syntactic constructor for the @code{} +structure. + +@item constructor +A symbol that will be used to define the traditional constructor. It is used in +the implementation of the syntactic constructor, but will not typically be used +elsewhere. The traditional @code{make-name} (for example, @code{make-package}) +is a fine value to use here. + +@item predicate +A symbol that will be used to test if a value is an instance of this record. +For example, @code{package?}. + +@item this-identifier +This symbol can be used when defining fields that need to refer to the struct +that contains them. For an example of this, see the @code{thunked} field +property, below. + +@item fields +A set of field specifiers which take the following form: + +@lisp +(field-name field-getter properties ...) +@end lisp + +Each of the properties must have one of the following forms: + +@table @code +@item (default @var{value}) +Defines the default value for the field, if the user does not specify one using +the syntactic constructor. + +@item (innate) +Fields marked as innate will not be inherited from parent objects (see +Instantiating Records, below, for details of object inheritance). + +@item (sanitize @var{proc}) +The value given by the user will be passed into @var{proc} before being stored +in the object. For example, consider this struct definition: + +@lisp +(define-record-type* thing make-thing + thing? + this-thing + (name thing-name + (sanitize (lambda (value) + (cond ((string? value) value) + ((symbol? value) (symbol->string value)) + (else (throw 'bad! value))))))) +@end lisp + +When creating @code{thing} instances either a string or a symbol can be +supplied but it will always be stored as a string: + +@lisp +(string? (thing-name (thing (name "some-name")))) +@result{} #t +(string? (thing-name (thing (name 'some-name)))) +@result{} #t +(thing (name 1994)) +@result{} Throw to key `bad!' with args `(1994)'. +@end lisp + +@item (thunked) +Fields marked as @code{thunked} will actually compute the field's value in the +current dynamic extent which is useful when referring to fluids in a field's +value. Furthermore, that thunk can access the record it belongs to via the +@code{this-identifier}. For example: + +@lisp +(define-record-type* rectangle make-rectangle + rectangle? + this-rectangle + (width rectangle-width) + (height rectangle-height) + (area rectangle-area (thunked) + (default (* (rectangle-width this-rectangle) + (rectangle-height this-rectangle))))) + +(define base-rectangle + (rectangle + (width 2) + (height 4))) + +(define derived-rectangle + (rectangle + (inherit base) + (width 6))) + +(rectangle-area base-rectangle) +@result{} 8 + +(rectangle-area derived-rectangle +@result{} 24 +@end lisp + +@item (delayed) +Fields marked as @code{delayed} are similar to @code{thunked} fields, except +that they are effectively wrapped in a @code{(delay @dots{})} form. Note that +delayed fields cannot use @code{this-identifier}. +@end table +@end table +@end defmac + @node Invoking guix repl @section Invoking @command{guix repl}