* [PATCH v3] docs/match: pattern matcher example makeover @ 2023-02-01 13:09 Blake Shaw 2023-02-01 16:40 ` Maxime Devos 0 siblings, 1 reply; 8+ messages in thread From: Blake Shaw @ 2023-02-01 13:09 UTC (permalink / raw) To: guile-devel; +Cc: Blake Shaw [V3 update] @xref{sxml-match} had been commented out because it was interfering with emacs makeinfo. This ammends that change. Also adding `--` to breakup changelog into a more digestible format. Reviewing everything all else, everything appears fine. [V2 update] All changes have been squashed into a single commit Before I was able to render only the pdf, but it was unclear how to render an individual texinfo page. Since then I discovered [makeinfo] in emacs which solves this problem. There is some conflict between PDF and Texinfo, and Guile currently seems to just choose texinfo. This update to the past patch series tailors the markup to best suit Texinfo, where previously PDF was was the rendered reference to compare against. - docs/match: add pattern matching examples -- This commit introduces a set of examples to the documentation on pattern matching discussed in the Guix Days presentation which can be found here: https://xana.lepiller.eu/guix-days-2022/guix-days-2022-documentation.mp4 As discussed in the Guix Days presentation, and agreed upon during the corresponding brainstorming session, I'm introducing a set of examples that intend to demonstrate the basics of the Shinn/Wright pattern matcher, as well as touch on some of its subtleties. Most paragraphs will be broken up into smaller peices with examples interspersed throughout, according to the refactoring strategy of "graduated expression" presented in my talk. The goal is to express the pedagogical aim of examples naturally through graduality, reducing "wordiness" primarily by with examples rather than reducing what has already been written. My apologies for taking so long to get to work on this! This summer I was confronted with a unanticipated event that subsequently turned my life upside down, causing my contributions to be low. I hope to be increasing my contributions from here on, and fulfilling my obligations concerning the documentation. - docs/match: rm unquote-splicing as it interferes with textinfo -- - docs/match: add reverse nested list example -- - docs/match: match-let* unwrap example -- pdf now builds, but there are some spacing issues I want to correct in the PDF, that will be introduced next. - docs/fixup: @cindex was in the wrong place -- - style: clean-up newlines -- It appears that while the PDF needs additional newlines to be presentable, these appear to have a negative effect on the presentation of the texinfo doc. I don't know how to fix this, but from looking at the PDF, it appears that the strategy until now has been to privilege texinfo at the expense of PDF readability (the PDF is more or less "squished together") So in that regard, these edits make my past edits more in sync with past Guile docs. - examples: replace with didactic ex. that can copied & pasted -- The existing example can't be copied and pasted. This example both fixes the past one and improves on its relation to the text. - style: switch to "Indiana style", bracketing lets and clauses -- After spending much time looking at the examples in black & white to edit the texinfo document, it occurred to me just how much the brackets improve legibility. Therefore, I have decided to adopt the "Indiana" style of using brackets, used by Kent Dybvig, Dan Friedman and Will Byrd at Indiana University. Currently the docs use this style in some places but not in others. Considering some are color blind, and that few will have rigged their texinfo configuration to use rainbow-delimiters in the while reading documentation, I think this should be considered a general accessibility improvement. indentation: make consistent according to rule defined below If a new paragraph opens onto a new topic, it should naturally indent (i.e, no indentation markup is required) If a new paragraph is a continuation of the current subject, the markup @noident should be applied markup: replace @var with @code unless @var is a @defn argument The way that it renders in texinfo means that it renders @vars in uppercase, the way that is conventionally done for definition arguments. Therefore I've changed all @vars to @code unless @var is a @defn argument - remove: paragraph that referred to a since removed example -- fix: uncomment @xref{sxml-match} --- doc/ref/match.texi | 252 ++++++++++++++++++++++++++++++--------------- 1 file changed, 167 insertions(+), 85 deletions(-) diff --git a/doc/ref/match.texi b/doc/ref/match.texi index f5ea43118..4e657b976 100644 --- a/doc/ref/match.texi +++ b/doc/ref/match.texi @@ -23,71 +23,142 @@ The @code{(ice-9 match)} module provides a @dfn{pattern matcher}, written by Alex Shinn, and compatible with Andrew K. Wright's pattern matcher found in many Scheme implementations. -@cindex pattern variable -A pattern matcher can match an object against several patterns and -extract the elements that make it up. Patterns can represent any Scheme -object: lists, strings, symbols, records, etc. They can optionally contain -@dfn{pattern variables}. When a matching pattern is found, an -expression associated with the pattern is evaluated, optionally with all -pattern variables bound to the corresponding elements of the object: +@noindent A pattern matcher does precisely what the name implies: it +matches some arbitrary pattern, and returns some result accordingly. @example -(let ((l '(hello (world)))) - (match l ;; <- the input object - (('hello (who)) ;; <- the pattern - who))) ;; <- the expression evaluated upon matching -@result{} world +(define (english-base-ten->number name) + (match name + ('zero 0) + ('one 1) + ('two 2) + ('three 3) + ('four 4) + ('five 5) + ('six 6) + ('seven 7) + ('eight 8) + ('nine 9))) + +(english-base-ten->number 'six) +@result{} 6 + +(apply + (map english-base-ten->number '(one two three four))) +@result{} 10 @end example -In this example, list @var{l} matches the pattern @code{('hello (who))}, -because it is a two-element list whose first element is the symbol -@code{hello} and whose second element is a one-element list. Here -@var{who} is a pattern variable. @code{match}, the pattern matcher, -locally binds @var{who} to the value contained in this one-element -list---i.e., the symbol @code{world}. An error would be raised if -@var{l} did not match the pattern. +@page +@cindex pattern variable +@noindent Pattern matchers may contain @dfn{pattern variables}, +local bindings to all elements that match a pattern. -The same object can be matched against a simpler pattern: +@example +(let re ([ns '(one two three four 9)] [total 0]) + (match ns + [(e) (+ total (english-base-ten->number e))] + [(e . es) + (re es (+ total (english-base-ten->number e)))])) -@example -(let ((l '(hello (world)))) - (match l - ((x y) - (values x y)))) -@result{} hello -@result{} (world) +@result{} 19 @end example -Here pattern @code{(x y)} matches any two-element list, regardless of -the types of these elements. Pattern variables @var{x} and @var{y} are -bound to, respectively, the first and second element of @var{l}. - -Patterns can be composed, and nested. For instance, @code{...} +@noindent In this example, the list @code{ns} matches the pattern +@code{(e . es)}, where the pattern variable @code{e} corresponds +to the metaphoical "car" of @code{ns} and the pattern variable @code{es} +corresponds to the "cdr" of @code{ns}. + +@noindent A tail call @code{re} is then initiated and we "cdr" down the +list by recurring on the tail @code{es}, applying our matcher +@code{english-base-ten->number} to each element of @code{ns} until +only a single element @code{(e)} remains, causing the @code{total} +to be computed. In modern Scheme programming it is common to use +@code{match} in place of the more verbose but familiar combination +of @code{cond}, @code{car} and @code{cdr}, so it's important to +understand how these idioms translate. + +Patterns can be composed and nested. For instance, @code{...} (ellipsis) means that the previous pattern may be matched zero or more times in a list: @example -(match lst - (((heads tails ...) ...) - heads)) +(match '((a.0 b.0 c.0 ((1.0 2.0 3.0) x.0 y.0 z.0)) + (a.1 b.1 c.1 ((1.1 2.1 3.1) x.1 y.1 z.1))) + [((heads ... ((tails ...) . rest)) ...) + (begin + (format #t "heads: ~a ~%" heads) + (format #t "tails: ~a ~%" tails) + (format #t "rest: ~a ~%" rest))]) +@result{} +heads: ((a.0 b.0 c.0) (a.1 b.1 c.1)) +tails: ((1.0 2.0 3.0) (1.1 2.1 3.1)) +rest: ((x.0 y.0 z.0) (x.1 y.1 z.1)) @end example -@noindent -This expression returns the first element of each list within @var{lst}. -For proper lists of proper lists, it is equivalent to @code{(map car -lst)}. However, it performs additional checks to make sure that -@var{lst} and the lists therein are proper lists, as prescribed by the -pattern, raising an error if they are not. - -Compared to hand-written code, pattern matching noticeably improves -clarity and conciseness---no need to resort to series of @code{car} and -@code{cdr} calls when matching lists, for instance. It also improves -robustness, by making sure the input @emph{completely} matches the -pattern---conversely, hand-written code often trades robustness for -conciseness. And of course, @code{match} is a macro, and the code it -expands to is just as efficient as equivalent hand-written code. - -The pattern matcher is defined as follows: +@noindent A pattern matcher can match an object against several +patterns and extract the elements that make it up. + +@example +(match '((l1 . r1) (l2 . r2) (l3 . r3)) + [((left . right) ...) + (list left right)]) + +@result{} ((l1 l2 l3) (r1 r2 r3)) +@end example + +@example +(match '((1 . (a . b)) (2 . (c . d)) (3 . (e . f))) + [((key . (left . right)) ...) + (fold-right acons '() key right )]) + +@result{} ((1 . b) (2 . d) (3 . f)) +@end example + +@example +(match '(((a b c) e f g) 1 2 3) + [(((head ...) . rest) tails ...) + (acons tails head rest )]) + +@result {} (((1 2 3) a b c) e f g) +@end example + +Patterns can represent any Scheme object: lists, strings, symbols, +records, etc. + +@noindent When a matching pattern is found, an expression is evaluated +with pattern variables bound to the corresponding elements of the object. + +@example +(let re ([m #(a "b" c "d" e "f" g)]) + (match m + [(or (e) #(e)) e] + [(or #(e1 e2 es ...) + (e1 e2 es ...)) + (cons (cons e1 e2) + (re es))])) + +@result{} ((a . "b") (c . "d") (e . "f") . g) +@end example + +@example +(let re ([m '(a b c d e f g h i)]) + (match m + [(e) e] + [(e1 e2 es ...) + (acons e1 e2 (re es))])) + +@result{} ((a . b) (c . d) (e . f) (g . h) . i) +@end example + +@noindent Compared to hand-written code, pattern matching noticeably +improves clarity and conciseness---no need to resort to series of +@code{car} and @code{cdr} calls when matching lists, for instance. +It also improves robustness, by making sure the input @emph{completely} +matches the pattern---conversely, hand-written code often trades +robustness for conciseness. And of course, @code{match} is a macro, +and the code it expands to is just as efficient as equivalent +hand-written code. + +@noindent We define @code{match} as follows: @* @deffn {Scheme Syntax} match exp clause1 clause2 @dots{} Match object @var{exp} against the patterns in @var{clause1} @@ -96,9 +167,9 @@ value produced by the first matching clause. If no clause matches, throw an exception with key @code{match-error}. Each clause has the form @code{(pattern body1 body2 @dots{})}. Each -@var{pattern} must follow the syntax described below. Each body is an +@code{pattern} must follow the syntax described below. Each body is an arbitrary Scheme expression, possibly referring to pattern variables of -@var{pattern}. +@code{pattern}. @end deffn @c FIXME: Document other forms: @@ -114,7 +185,7 @@ arbitrary Scheme expression, possibly referring to pattern variables of @c @c clause ::= (pat body) | (pat => exp) -The syntax and interpretation of patterns is as follows: +@noindent @* The pattern language is specified as follows: @* @verbatim patterns: matches: @@ -176,12 +247,12 @@ qp ::= () the empty list | ,@pat a pattern @end verbatim -The names @code{quote}, @code{quasiquote}, @code{unquote}, +@noindent The names @code{quote}, @code{quasiquote}, @code{unquote}, @code{unquote-splicing}, @code{?}, @code{_}, @code{$}, @code{and}, @code{or}, @code{not}, @code{set!}, @code{get!}, @code{...}, and @code{___} cannot be used as pattern variables. -Here is a more complex example: +Here is a more complex example using records and promises: @example (use-modules (srfi srfi-9)) @@ -193,24 +264,24 @@ Here is a more complex example: (name person-name) (friends person-friends)) - (letrec ((alice (make-person "Alice" (delay (list bob)))) - (bob (make-person "Bob" (delay (list alice))))) + (letrec ([alice (make-person "Alice" (delay (list bob)))] + [bob (make-person "Bob" (delay (list alice)))]) (match alice - (($ person name (= force (($ person "Bob")))) - (list 'friend-of-bob name)) - (_ #f)))) + [($ person name (= force (($ person "Bob")))) + (list 'friend-of-bob name)] + [_ #f]))) @result{} (friend-of-bob "Alice") @end example @noindent Here the @code{$} pattern is used to match a SRFI-9 record of type -@var{person} containing two or more slots. The value of the first slot -is bound to @var{name}. The @code{=} pattern is used to apply +@code{person} containing two or more slots. The value of the first slot +is bound to @code{name}. The @code{=} pattern is used to apply @code{force} on the second slot, and then checking that the result matches the given pattern. In other words, the complete pattern matches -any @var{person} whose second slot is a promise that evaluates to a -one-element list containing a @var{person} whose first slot is +any @code{person} whose second slot is a promise that evaluates to a +one-element list containing a @code{person} whose first slot is @code{"Bob"}. The @code{(ice-9 match)} module also provides the following convenient @@ -229,11 +300,11 @@ expressions. @end deffn @example -((match-lambda - (('hello (who)) - who)) - '(hello (world))) -@result{} world +(define flatten-singletons + (match-lambda [((s) ...) s])) + +(flatten-singletons '((x) (y) (z))) +@result{} (x y z) @end example @deffn {Scheme Syntax} match-lambda* clause1 clause2 @dots{} @@ -264,11 +335,10 @@ and can also be used for recursive functions which match on their arguments as in @code{match-lambda*}. @example -(match-let (((x y) (list 1 2)) - ((a b) (list 3 4))) - (list a b x y)) -@result{} -(3 4 1 2) +(match-let ([(x y ...) (list 1 2 3)] + [(a b ...) (list 3 4 5)]) + (list x a y b)) +@result{} (1 3 (2 3) (4 5)) @end example @end deffn @@ -287,22 +357,34 @@ Similar to @code{match-let}, but analogously to @code{let*}, match and bind the variables in sequence, with preceding match variables in scope. @example -(match-let* (((x y) (list 1 2)) - ((a b) (list x 4))) - (list a b x y)) +(match-let* ([(x . y) (list 1 2 3)] + [(a . b) (list x 4 y)]) + (list a b)) @equiv{} -(match-let (((x y) (list 1 2))) - (match-let (((a b) (list x 4))) - (list a b x y))) -@result{} -(1 4 1 2) +(match-let ([(x . y) (list 1 2)]) + (match-let ([(a . b) (list x 4 y)]) + (list a b))) +@result{} (1 (4 (2 3))) @end example @end deffn +@example +(define wrap '(((((unnest arbitrary nestings)))))) + +(let unwrap ([peel wrap]) + (match-let* ([([core ...]) peel] + [(wrapper ...) core]) + (if (> (length wrapper) 1) + wrapper + (unwrap wrapper)))) + +@result{} (unnest arbitrary nestings) +@end example + @deffn {Scheme Syntax} match-letrec ((variable expression) @dots{}) body Similar to @code{match-let}, but analogously to @code{letrec}, match and bind the variables with all match variables in scope. @end deffn -Guile also comes with a pattern matcher specifically tailored to SXML -trees, @xref{sxml-match}. +@noindent Guile also comes with a pattern matcher specifically +tailored to SXML trees, @xref{sxml-match}. -- 2.39.1 ^ permalink raw reply related [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-01 13:09 [PATCH v3] docs/match: pattern matcher example makeover Blake Shaw @ 2023-02-01 16:40 ` Maxime Devos 2023-02-02 17:47 ` David Pirotte 0 siblings, 1 reply; 8+ messages in thread From: Maxime Devos @ 2023-02-01 16:40 UTC (permalink / raw) To: Blake Shaw, guile-devel [-- Attachment #1.1.1: Type: text/plain, Size: 22572 bytes --] On 01-02-2023 14:09, Blake Shaw wrote: > [...] > - > style: clean-up newlines > -- > It appears that while the PDF needs additional newlines > to be presentable, these appear to have a negative effect > on the presentation of the texinfo doc. > > I don't know how to fix this, but from looking at the PDF, > it appears that the strategy until now has been to privilege > texinfo at the expense of PDF readability (the PDF is more > or less "squished together") > > So in that regard, these edits make my past edits more in sync > with past Guile docs. IIRC, Texinfo has a @iftex @endif construct or such. You could use this to define a @pdf-newline macro, to only insert newlines in the PDF (TeX is used for the PDF). > - > examples: replace with didactic ex. that can copied & pasted > -- > The existing example can't be copied and pasted. > > This example both fixes the past one and improves on its relation > to the text. > > - > style: switch to "Indiana style", bracketing lets and clauses > -- > After spending much time looking at the examples in black & white > to edit the texinfo document, it occurred to me just how much the > brackets improve legibility. Therefore, I have decided to adopt > the "Indiana" style of using brackets, used by Kent Dybvig, Dan > Friedman and Will Byrd at Indiana University. > > Currently the docs use this style in some places but not in others. > > Considering some are color blind, and that few will have rigged > their texinfo configuration to use rainbow-delimiters in the while > reading documentation, I think this should be considered a general > accessibility improvement. IME, (( )) is quite readable (and I don't use rainbow delimiters). That might largely be 'due to experience', though. While I would expect ([ ] [ ]) to be unconventional for many Guilers, it should be readable too though, so I suppose it could be good to just change the convention, then. You are currently making the manual more inconsistent by using this (for Guile) mostly non-standard notation though; IIRC the manual mostly does (( )) and not ([ ]). Yet, in the review of the v1, you mentioned > No, I'm not, I'm being totally boring and normal in this regard because collectively authored documentation is something you should never adopt non-standard writing notation in the course of authoring, just to one up someone on a mailing list > > To be honest, it's this kind of attitude that has resulted in the current docs that so many people find utterly incomprehensible. The core point of my talk that what makes Info Guile so hard to read is the lack of stylistic consistency. Editors and editing exist for a very good reason. , which is very much against non-standard notation and for consistency. As such, I propose: a) Before (or after) this patch, change everything in the manual to "Indiana style", for consistency. If you go for 'after this patch', I mean immediately afterwards, because Guile contributors tend to come and go, and delaying things tends to become never doing things. b) or: do it in non-Indiana style (likely not the option you will take, but it would be more stylistically consistent than the current version of the patch ...) c) or: don't adjust everything in the manual to Indiana style yet, but also make it a rule that the manual (and Guile code in Guile proper, I guess) does Indiana style, and that all current deviations from Indiana style are old style to be updated in the future. If this were Guix, you could make this a rule by adding it to the "Contributing" section. Guile does not have appear to have such a section, but "1.8 Typograhical Conventions" might be a good place. Additionally, changing the parenthesis convention in Guile is not just a change to the 'match' documentation, but the subject line only mentions 'match'. While Indiana styles seems a good thing to me at first sight now you mention the benefits, it needs a separate e-mail thread such that people interested in ()/[] stuff but not in 'match' stuff will have an opportunity to respond. > indentation: make consistent according to rule defined below > > If a new paragraph opens onto a new topic, it should naturally > indent (i.e, no indentation markup is required) > > If a new paragraph is a continuation of the current subject, > the markup @noident should be applied > > markup: replace @var with @code unless @var is a @defn argument > > The way that it renders in texinfo means that it renders @vars > in uppercase, the way that is conventionally done for definition > arguments. I'm not too familiar with Texinfo PDF output but I'll take your word for it. However, this is not the case at least for HTML output, as you can see at <https://www.gnu.org/software/texinfo/manual/texinfo/html_node/_0040var.html>, for HTML documentation it remains lowercase. > Therefore I've changed all @vars to @code unless @var is a @defn > argument I'm missing what you mean with the 'Therefore'. How does this relate to your previous paragraph (I don't get what your point is about 'definition arguments')? Do you mean that uppercase @var bad and that it should be lowercase instead? If so, it would be better to modify Texinfo itself to let @var not change the case, then every manual in Texinfo would benefit instead of only the Guile manual. Also, you could ask the Texinfo people if there is a reason for uppercase @var; maybe they determined that it is more readable to more people (I'm just speculating, I don't know the reason)? -- Presumably there's some good reason (or maybe not, I don't know, but you could ask them first). Otherwise, if you make this Guile-specific change, you would create stylistical inconsistencies between projects using Texinfo. More specifically, you are creating stylistical inconsisencies between GNU projects. Additionally, you are not merely removing the uppercasing thing, you are also removing the 'slanted' thing -- the result of @var is slanted typewriter, the result of @code is merely typewriter, which makes it slightly harder to distinguish metavariables from other code. You are also only making this stylistical change in the documentation of 'match'; the remainder of the manual still has the old @var. If you change tings, it would be better to change things for the whole manual. I think you can do this by redefining the @var macro to whatever you want in the prelude (at least that can be done in TeX). > - > remove: paragraph that referred to a since removed example > -- > > fix: uncomment @xref{sxml-match} > --- > doc/ref/match.texi | 252 ++++++++++++++++++++++++++++++--------------- > 1 file changed, 167 insertions(+), 85 deletions(-) > > diff --git a/doc/ref/match.texi b/doc/ref/match.texi > index f5ea43118..4e657b976 100644 > --- a/doc/ref/match.texi > +++ b/doc/ref/match.texi > @@ -23,71 +23,142 @@ The @code{(ice-9 match)} module provides a @dfn{pattern matcher}, > written by Alex Shinn, and compatible with Andrew K. Wright's pattern > matcher found in many Scheme implementations. > > -@cindex pattern variable > -A pattern matcher can match an object against several patterns and > -extract the elements that make it up. Patterns can represent any Scheme > -object: lists, strings, symbols, records, etc. They can optionally contain > -@dfn{pattern variables}. When a matching pattern is found, an > -expression associated with the pattern is evaluated, optionally with all > -pattern variables bound to the corresponding elements of the object: > +@noindent A pattern matcher does precisely what the name implies: it > +matches some arbitrary pattern, and returns some result accordingly. Again, as I mentioned previously, in the general case it matches arbitrary patterns (plural) and returns results (plural) -- the 'match' construct is not as limited as you are implying it to be here. > > @example > -(let ((l '(hello (world)))) > - (match l ;; <- the input object > - (('hello (who)) ;; <- the pattern > - who))) ;; <- the expression evaluated upon matching > -@result{} world > +(define (english-base-ten->number name) > + (match name > + ('zero 0) > + ('one 1) > + ('two 2) > + ('three 3) > + ('four 4) > + ('five 5) > + ('six 6) > + ('seven 7) > + ('eight 8) > + ('nine 9))) > + > +(english-base-ten->number 'six) > +@result{} 6 My previous comment still applies: > This is a suboptimal example; this would be better done with 'case'. > I propose replacing it with another example, or adding a note that one would normally use 'case' for this. still applies. What is the reason for not doing something akin to that? > + > +(apply + (map english-base-ten->number '(one two three four))) > +@result{} 10 > @end example > > -In this example, list @var{l} matches the pattern @code{('hello (who))}, > -because it is a two-element list whose first element is the symbol > -@code{hello} and whose second element is a one-element list. Here > -@var{who} is a pattern variable. @code{match}, the pattern matcher, > -locally binds @var{who} to the value contained in this one-element > -list---i.e., the symbol @code{world}. An error would be raised if > -@var{l} did not match the pattern. > +@page > +@cindex pattern variable > +@noindent Pattern matchers may contain @dfn{pattern variables}, > +local bindings to all elements that match a pattern. 'Pattern matchers' -> 'pattern' would be more precise here, as it more precisely states _where_ the pattern variable is. E.g. if you say 'pattern', it's certainly not the 'ns' in (match ns ...). If you say 'pattern matcher' (*), then 'pattern matcher' might mean 'match' itself, or (match ns ...); the former does not contain a pattern variable, the latter likely does but less is stated about _where_ the pattern variable is, purely going by your sentence it moght be the 'match' which is incorrect. (*) While the original text defined 'pattern matcher=match', that part doesn't contain any pattern variables, and in your new text the notion is of 'pattern matcher' is not exactly defined but rather described, and not as some kind of precise characterisation. > > -The same object can be matched against a simpler pattern: > +@example > +(let re ([ns '(one two three four 9)] [total 0]) The Scheme convention would to be to write 'loop' instead of 're' when using named-let, and something like 'rest' instead of 'ns'. The exact word for the loop argument varies a lot, but two letters that don't appear to mean anything are to be avoided. > + (match ns > + [(e) (+ total (english-base-ten->number e))] > + [(e . es) > + (re es (+ total (english-base-ten->number e)))])) I tried running your example, and it doesn't work: (define (english-base-ten->number name) (match name ('zero 0) ('one 1) ('two 2) ('three 3) ('four 4) ('five 5) ('six 6) ('seven 7) ('eight 8) ('nine 9))) (let re ([ns '(one two three four 9)] [total 0]) (match ns [(e) (+ total (english-base-ten->number e))] [(e . es) (re es (+ total (english-base-ten->number e)))])) ice-9/boot-9.scm:1685:16: In procedure raise-exception: Throw to key `match-error' with args `("match" "no matching pattern" 9)'. Entering a new prompt. Type `,bt' for a backtrace or `,q' to continue. I think you need to replace (one two three four 9) by (one two three four nine). As you mentioned yourself (in other words), examples in the manual should actually work as-is. > -@example > -(let ((l '(hello (world)))) > - (match l > - ((x y) > - (values x y)))) > -@result{} hello > -@result{} (world) > +@result{} 19 > @end example > > -Here pattern @code{(x y)} matches any two-element list, regardless of > -the types of these elements. Pattern variables @var{x} and @var{y} are > -bound to, respectively, the first and second element of @var{l}. > - > -Patterns can be composed, and nested. For instance, @code{...} > +@noindent In this example, the list @code{ns} matches the pattern > +@code{(e . es)}, where the pattern variable @code{e} corresponds > +to the metaphoical "car" of @code{ns} and the pattern variable @code{es} > +corresponds to the "cdr" of @code{ns}. Typo: metaphoical -> metaphorical. Also: metaphorical -> literal. -- e is literally the car of ns (or ‘corresponds to the car of ns in a literal way’ if you go for a variable/value distinction); there is nothing figurative here. I would just drop the metaphorical/literal word. Also, "car" -> `car' and "cdr" -> `cdr' -- the manual currently consistently uses the quotation style ‘car’ / ‘pair?’, ‘SCM’, ..., not "car". For example, in 5.4.1 Dynamic Types, there is the paragraph: > In order to implement standard Scheme functions like ‘pair?’ and > ‘string?’ and provide garbage collection, the representation of every > value must contain enough information to accurately determine its type > at run time. 'Function' -> 'Procedure'. You are introducing a stylistical inconsistency here. In Guile, the C things are called 'Functions', and the Scheme things are called 'Procedures'. To some degree, this ‘in Scheme it's called a procedure’ also holds for other Schemes IIUC. Actually, while some GC do require runtime type information (RTI), RTI is not needed for garbage collection. Guix uses Boehm-GC for garbage collection. Being a conservative garbage collector, it doesn't need any type information. It works a little better if you do give it some type information, and Guile does give it some information in some cases, but it's not required. This information is therefore incorrect and needs to be removed, but the bits about predicates seems fine to me. > Often, Scheme systems also use this information to > determine whether a program has attempted to apply an operation to an > inappropriately typed value (such as taking the ‘car’ of a string). IIUC, in Texinfo, we write `stuff' instead of ‘stuff’, and it will get turned in ‘stuff’. I dunno why this is still done in the Guile manual as UTF-8 is an established thing, but I have used ‘’ in Guix stuff in the past and people changed into `'. Additionally, doing "git grep -F "car" doc/ref/*.texi", it appears that the manual doesn't actually quote car and cdr -- instead it writes car and cdr unquoted, or writes @code{car} / @code{cdr} which happens to be turned into a quoted ‘car’ / ‘cdr’ in the .info documentation by Texinfo. I think you can guess what I would be saying about stylistic consistency here. > + > +@noindent A tail call @code{re} is then initiated ‘A tail call @code{re} is then initiated’ -> ‘A tail call to @code{re} is the initiated’ -- @code{re} is a variable reference, not a tail call. The tail call is @code{(re es (+ to total ...))}. More simply, you could write ‘The procedure @var{re} is then tail-called’. > +and we "cdr" down the > +list by recurring on the tail @code{es}, applying our matcher > +@code{english-base-ten->number} to each element of @code{ns} until > +only a single element @code{(e)} remains, causing the @code{total} > +to be computed. In modern Scheme programming it is common to use > +@code{match} in place of the more verbose but familiar combination > +of @code{cond}, @code{car} and @code{cdr}, so it's important to > +understand how these idioms translate. > + > +Patterns can be composed and nested. For instance, @code{...} > (ellipsis) means that the previous pattern may be matched zero or more > times in a list: > @example > -(match lst > - (((heads tails ...) ...) > - heads)) > +(match '((a.0 b.0 c.0 ((1.0 2.0 3.0) x.0 y.0 z.0)) > + (a.1 b.1 c.1 ((1.1 2.1 3.1) x.1 y.1 z.1))) > + [((heads ... ((tails ...) . rest)) ...) > + (begin > + (format #t "heads: ~a ~%" heads) > + (format #t "tails: ~a ~%" tails) > + (format #t "rest: ~a ~%" rest))]) > +@result{} > +heads: ((a.0 b.0 c.0) (a.1 b.1 c.1)) > +tails: ((1.0 2.0 3.0) (1.1 2.1 3.1)) > +rest: ((x.0 y.0 z.0) (x.1 y.1 z.1)) > @end example > > -@noindent > -This expression returns the first element of each list within @var{lst}. > -For proper lists of proper lists, it is equivalent to @code{(map car > -lst)}. However, it performs additional checks to make sure that > -@var{lst} and the lists therein are proper lists, as prescribed by the > -pattern, raising an error if they are not. > - > -Compared to hand-written code, pattern matching noticeably improves > -clarity and conciseness---no need to resort to series of @code{car} and > -@code{cdr} calls when matching lists, for instance. It also improves > -robustness, by making sure the input @emph{completely} matches the > -pattern---conversely, hand-written code often trades robustness for > -conciseness. And of course, @code{match} is a macro, and the code it > -expands to is just as efficient as equivalent hand-written code. > - > -The pattern matcher is defined as follows: > +@noindent A pattern matcher can match an object against several > +patterns and extract the elements that make it up. > + > +@example > +(match '((l1 . r1) (l2 . r2) (l3 . r3)) > + [((left . right) ...) > + (list left right)]) > + > +@result{} ((l1 l2 l3) (r1 r2 r3)) > +@end example > + > +@example > +(match '((1 . (a . b)) (2 . (c . d)) (3 . (e . f))) > + [((key . (left . right)) ...) > + (fold-right acons '() key right )]) > + > +@result{} ((1 . b) (2 . d) (3 . f)) > +@end example > + > +@example > +(match '(((a b c) e f g) 1 2 3) > + [(((head ...) . rest) tails ...) > + (acons tails head rest )]) > + > +@result {} (((1 2 3) a b c) e f g) > +@end example > + > +Patterns can represent any Scheme object: lists, strings, symbols, > +records, etc. > + > +@noindent When a matching pattern is found, an expression is evaluated > +with pattern variables bound to the corresponding elements of the object. > + > +@example > +(let re ([m #(a "b" c "d" e "f" g)]) > + (match m > + [(or (e) #(e)) e] > + [(or #(e1 e2 es ...) > + (e1 e2 es ...)) > + (cons (cons e1 e2) > + (re es))])) > + > +@result{} ((a . "b") (c . "d") (e . "f") . g) > +@end example > + > +@example > +(let re ([m '(a b c d e f g h i)]) > + (match m > + [(e) e] > + [(e1 e2 es ...) > + (acons e1 e2 (re es))])) > + > +@result{} ((a . b) (c . d) (e . f) (g . h) . i) > +@end example > + > +@noindent Compared to hand-written code, pattern matching noticeably > +improves clarity and conciseness---no need to resort to series of > +@code{car} and @code{cdr} calls when matching lists, for instance. > +It also improves robustness, by making sure the input @emph{completely} > +matches the pattern---conversely, hand-written code often trades > +robustness for conciseness. And of course, @code{match} is a macro, > +and the code it expands to is just as efficient as equivalent > +hand-written code. > + > +@noindent We define @code{match} as follows: @* Why did you change this from The pattern matcher is defined as follows: ? While the 'we' / 'our' / ... construct is pretty convenient, IMO it is better avoided as long as the avoidance doesn't lead to awkward constructions. > @deffn {Scheme Syntax} match exp clause1 clause2 @dots{} > Match object @var{exp} against the patterns in @var{clause1} > @@ -96,9 +167,9 @@ value produced by the first matching clause. If no clause matches, > throw an exception with key @code{match-error}. > > Each clause has the form @code{(pattern body1 body2 @dots{})}. Each > -@var{pattern} must follow the syntax described below. Each body is an > +@code{pattern} must follow the syntax described below. Each body is an > arbitrary Scheme expression, possibly referring to pattern variables of > -@var{pattern}. > +@code{pattern}. > @end deffn > > @c FIXME: Document other forms: > @@ -114,7 +185,7 @@ arbitrary Scheme expression, possibly referring to pattern variables of > @c > @c clause ::= (pat body) | (pat => exp) > > -The syntax and interpretation of patterns is as follows: > +@noindent @* The pattern language is specified as follows: @* The stuff below still defines the interpretation, not only the language/grammar. The change 'syntax -> language' seems fine to me, but why remove 'interpretation'? Additionally, I personally would go for interpretation->semantics, but maybe that's too obscure for a general audience. > [...]> @deffn {Scheme Syntax} match-lambda* clause1 clause2 @dots{} > @@ -264,11 +335,10 @@ and can also be used for recursive functions which match on their > arguments as in @code{match-lambda*}. > > @example > -(match-let (((x y) (list 1 2)) > - ((a b) (list 3 4))) > - (list a b x y)) > -@result{} > -(3 4 1 2) > +(match-let ([(x y ...) (list 1 2 3)] > + [(a b ...) (list 3 4 5)]) > + (list x a y b)) > +@result{} (1 3 (2 3) (4 5)) > @end example > @end deffn > > @@ -287,22 +357,34 @@ Similar to @code{match-let}, but analogously to @code{let*}, match and > bind the variables in sequence, with preceding match variables in scope. > > @example > -(match-let* (((x y) (list 1 2)) > - ((a b) (list x 4))) > - (list a b x y)) > +(match-let* ([(x . y) (list 1 2 3)] > + [(a . b) (list x 4 y)]) > + (list a b)) > @equiv{} The old example was simpler and still fully demonstrated 'match-let*', why the change (besides [])? >[...] > > +@example > +(define wrap '(((((unnest arbitrary nestings)))))) > + > +(let unwrap ([peel wrap]) > + (match-let* ([([core ...]) peel] > + [(wrapper ...) core]) > + (if (> (length wrapper) 1) > + wrapper > + (unwrap wrapper)))) > + > +@result{} (unnest arbitrary nestings) > +@end example > + (Not saying anything about this example TBC.) Greetings, Maxime. [-- Attachment #1.1.2: OpenPGP public key --] [-- Type: application/pgp-keys, Size: 929 bytes --] [-- Attachment #2: OpenPGP digital signature --] [-- Type: application/pgp-signature, Size: 236 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-01 16:40 ` Maxime Devos @ 2023-02-02 17:47 ` David Pirotte 2023-02-03 10:05 ` Blake Shaw 2023-02-03 13:43 ` Josselin Poiret 0 siblings, 2 replies; 8+ messages in thread From: David Pirotte @ 2023-02-02 17:47 UTC (permalink / raw) To: Maxime Devos; +Cc: Blake Shaw, guile-devel [-- Attachment #1: Type: text/plain, Size: 807 bytes --] > > - > > style: switch to "Indiana style", bracketing lets and clauses > > -- > > After spending much time looking at the examples in black & white > > to edit the texinfo document, it occurred to me just how much the > > brackets improve legibility. Not at all - and quite annoying. imo. So i'd 'vote' not to use them anywhere in the guile reference manual David. It makes the code appear as if it was another language, and requires you train yourself to ignore them - as they actually do _not_ have any specific meaning, but nonetheless 'pretend they do', by their mere presence, and this causes a reading nuisance. They are also often used inconsistently by those who use them, as where/how you'd use them is a matter of taste, which further adds to the reading nuisance, imo. [-- Attachment #2: OpenPGP digital signature --] [-- Type: application/pgp-signature, Size: 488 bytes --] ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-02 17:47 ` David Pirotte @ 2023-02-03 10:05 ` Blake Shaw 2023-02-03 12:05 ` Arun Isaac 2023-02-03 13:43 ` Josselin Poiret 1 sibling, 1 reply; 8+ messages in thread From: Blake Shaw @ 2023-02-03 10:05 UTC (permalink / raw) To: David Pirotte; +Cc: Maxime Devos, guile-devel David Pirotte <david@altosw.be> writes: > Not at all - and quite annoying. imo. > So i'd 'vote' not to use them anywhere in the guile reference manual take the following functions in both styles: (let unwrap ((peel '(((((unnest arbitrary nestings))))))) (match-let* (((core ...)) peel) ((wrapper ...) core)) (if (> (length wrapper) 1) wrapper (unwrap wrapper)))) (let () (define-record-type person (make-person name friends) person? (name person-name) (friends person-friends)) (letrec ((alice (make-person "Alice" (delay (list bob)))) (bob (make-person "Bob" (delay (list alice))))) (match alice (($ person name (= force (($ person "Bob")))) (list 'friend-of-bob name))))) (let unwrap ([peel '(((((unnest arbitrary nestings)))))]) (match-let* ([([core ...]) peel] [(wrapper ...) core]) (if (> (length wrapper) 1) wrapper (unwrap wrapper)))) (let () (define-record-type person (make-person name friends) person? (name person-name) (friends person-friends)) (letrec ([alice (make-person "Alice" (delay (list bob)))] [bob (make-person "Bob" (delay (list alice)))] (match alice [($ person name (= force (($ person "Bob")))) (list 'friend-of-bob name)]))) Without copy and pasting, which have errors, and where? My wager is that unless you are already quite well adjusted to lisp, its much easier to catch the errors in the "Indiana" style examples, and if you are quite well adjusted to lisp, you aren't really impacted by these conventions in documentation in any concrete, meaninglful way. > It makes the code appear as if it was another language, and requires > you train yourself to ignore them - as they actually do _not_ have any > specific meaning, but nonetheless 'pretend they do', by their mere > presence, and this causes a reading nuisance. Well, these conventions can be found throughout the gamut of scheme literature going back to the 80s, and some of the largest scheme projects, such as Chez, Racket, etc. employ them. So if you're getting into Scheme, you'll necessarily encounter them, and if you haven't been made aware that brackets are syntactic sugar for parens in Scheme, or if that doesn't become apparent with some quick repl experimentation, you've probably jumped into pattern matching a bit too quickly. But overall, it seems the objections against the Indiana style here are primarily concerned with individual, current user/contributor preferences, rather than out of a concern for the target audience, which are newcomers. ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-03 10:05 ` Blake Shaw @ 2023-02-03 12:05 ` Arun Isaac 2023-02-03 13:10 ` Blake Shaw 0 siblings, 1 reply; 8+ messages in thread From: Arun Isaac @ 2023-02-03 12:05 UTC (permalink / raw) To: Blake Shaw, David Pirotte; +Cc: Maxime Devos, guile-devel Hi Blake, > Well, these conventions can be found throughout the gamut of scheme > literature going back to the 80s, and some of the largest scheme > projects, such as Chez, Racket, etc. employ them. So if you're > getting into Scheme, you'll necessarily encounter them, and if you > haven't been made aware that brackets are syntactic sugar for parens > in Scheme, or if that doesn't become apparent with some quick repl > experimentation, you've probably jumped into pattern matching a bit > too quickly. > > But overall, it seems the objections against the Indiana style here > are primarily concerned with individual, current user/contributor > preferences, rather than out of a concern for the target audience, > which are newcomers. I don't think I agree. When I was a newcomer to guile and was reading the sxml-match documentation in the manual for the first time, I found it very confusing that there were square brackets. At that point, I understood match but was confounded into thinking that sxml-match was completely different due to the square brackets. Finally, when I understood, I contributed a patch making everything round parentheses. https://issues.guix.gnu.org/30920 I'd say a typical newcomer is not familiar with the gamut of scheme literature going back to the 80s. I certainly wasn't, and still am not to be honest. Cheers! Arun ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-03 12:05 ` Arun Isaac @ 2023-02-03 13:10 ` Blake Shaw 0 siblings, 0 replies; 8+ messages in thread From: Blake Shaw @ 2023-02-03 13:10 UTC (permalink / raw) To: Arun Isaac; +Cc: David Pirotte, Maxime Devos, guile-devel Arun Isaac <arunisaac@systemreboot.net> writes: > Hi Blake, > >> Well, these conventions can be found throughout the gamut of scheme >> literature going back to the 80s, and some of the largest scheme >> projects, such as Chez, Racket, etc. employ them. So if you're >> getting into Scheme, you'll necessarily encounter them, and if you >> haven't been made aware that brackets are syntactic sugar for parens >> in Scheme, or if that doesn't become apparent with some quick repl >> experimentation, you've probably jumped into pattern matching a bit >> too quickly. >> >> But overall, it seems the objections against the Indiana style here >> are primarily concerned with individual, current user/contributor >> preferences, rather than out of a concern for the target audience, >> which are newcomers. > > I don't think I agree. When I was a newcomer to guile and was reading > the sxml-match documentation in the manual for the first time, I found > it very confusing that there were square brackets. At that point, I > understood match but was confounded into thinking that sxml-match was > completely different due to the square brackets. Finally, when I > understood, I contributed a patch making everything round > parentheses. https://issues.guix.gnu.org/30920 > > I'd say a typical newcomer is not familiar with the gamut of scheme > literature going back to the 80s. I certainly wasn't, and still am not > to be honest. Point taken, but I'd remark that I didn't mean that newcomers should be familiar with existing literature, but rather that its common convention with roots going way back, rather than an ad-hoc notation. > > Cheers! > Arun ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-02 17:47 ` David Pirotte 2023-02-03 10:05 ` Blake Shaw @ 2023-02-03 13:43 ` Josselin Poiret 2023-02-03 14:14 ` Blake Shaw 1 sibling, 1 reply; 8+ messages in thread From: Josselin Poiret @ 2023-02-03 13:43 UTC (permalink / raw) To: David Pirotte, Maxime Devos; +Cc: Blake Shaw, guile-devel Hi Blake and David, We were talking about this very node of the documentation yesterday with Ludovic and zimoun, so here are my two cents. Rewriting this introduction is a very good idea, the current one is pretty hard to get into for novices. David Pirotte <david@altosw.be> writes: > Not at all - and quite annoying. imo. > So i'd 'vote' not to use them anywhere in the guile reference manual I agree that it's not "idiomatic Guile" so should probably be left out. Regarding the examples, I think the first one is nice but the next one is too involved, using a named let which a lot of users might not know. I'd suggest demonstrating each feature without any extra prerequisite, to make it as accessible as possible. The third example, introducing the ellipsis, uses 2 of them directly, with one nested! It also doesn't explain what the pattern variables are bound to when an ellipsis is involved. Also, the example data you're matching on looks too intimidating, which could scare novice readers. Best, -- Josselin Poiret ^ permalink raw reply [flat|nested] 8+ messages in thread
* Re: [PATCH v3] docs/match: pattern matcher example makeover 2023-02-03 13:43 ` Josselin Poiret @ 2023-02-03 14:14 ` Blake Shaw 0 siblings, 0 replies; 8+ messages in thread From: Blake Shaw @ 2023-02-03 14:14 UTC (permalink / raw) To: Josselin Poiret; +Cc: David Pirotte, Maxime Devos, guile-devel Josselin Poiret <dev@jpoiret.xyz> writes: > Hi Blake and David, > > We were talking about this very node of the documentation yesterday with > Ludovic and zimoun, so here are my two cents. Rewriting this > introduction is a very good idea, the current one is pretty hard to get > into for novices. > > David Pirotte <david@altosw.be> writes: > >> Not at all - and quite annoying. imo. >> So i'd 'vote' not to use them anywhere in the guile reference manual > > I agree that it's not "idiomatic Guile" so should probably be left out. To avoid bikeshedding, I've reverted back to not use brackets. > > Regarding the examples, I think the first one is nice but the next one > is too involved, using a named let which a lot of users might not know. > I'd suggest demonstrating each feature without any extra prerequisite, > to make it as accessible as possible. > I agree that it escalates quickly. I was hoping to juxtapose the obvious with whats more involved in hopes of hitting a "sweet spot" where the total novice can go through the examples one-by-one and still comprehend them, while an experienced functional programmer without Scheme/Guile experience can jump straight in, review a few examples, catch the drift and keep working on what they are working on, without much reading. I wanted to go straight from an arbitrary example to one with recursion in order to show how to think "heads/tails" recursively with a Scheme matcher, because I personally found it initially intimidating to start doing recursive pattern matching in Scheme, and found few examples that do so in a highly simplified form, making it confusing to translate knowlege from other functional languages. From my POV, named let seems like a pretty standard feature of Scheme, and its used throughout the docs (perhaps that isn't a good thing). What if I followed it with an equivalent conditional version, and used the more explicit "recur" or "lp" or "loop" as the let's name? smth like: ... ≣ (let recur ((ns '(one two three four nine)) (total 0)) (if (equal? 1 (length ns)) (+ total (english-base-ten->number (car ns))) (recur (cdr ns) (+ total (english-base-ten->number (car ns)))))) > The third example, introducing the ellipsis, uses 2 of them directly, > with one nested! It also doesn't explain what the pattern variables are > bound to when an ellipsis is involved. just before the third example we have: -- `Patterns can be composed and nested. For instance, @code{...} (ellipsis) means that the previous pattern may be matched zero or more times in a list:` -- How would you improve on this? Also it does explain what the pattern variables are bound to, but as the result rather than in writing, which I think makes for more digestible "reference" material. > Also, the example data you're > matching on looks too intimidating, which could scare novice readers. I think this one may look complicated from afar, but I added it to [V2] because it explicates something that can't be demonstrated from simpler data and I think is often missed: that ellipsis allow pattern variables to bind every for every instance of a pattern. I chose to show this immediately, followed by simpler examples, so that the reader is exposed this idea upfront. take a simpler example: (match '(a b (1 2 3 )) ((heads ... (tails ...)) (format #t "heads: ~a ~%tails: ~a ~%" heads tails))) ⇒ heads: (a b) tails: (1 2 3) Psychologically, it is easy for the reader to not pickup on the fact that ellipsis allow you to match over nested patterns. This may cause readers to resort to using match with map when its unneccessary. The below contains two lists, (a.n b.n ((1.n 2.n 3.n) x.n y.n z.n)), where the the letters and numbers follow an obvious order, and the results show the effect of nesting patterns. (match '((a.0 b.0 c.0 ((1.0 2.0 3.0) x.0 y.0 z.0)) (a.1 b.1 c.1 ((1.1 2.1 3.1) x.1 y.1 z.1))) (((heads ... ((tails ...) . rest)) ...) (begin (format #t "heads: ~a ~%" heads) (format #t "tails: ~a ~%" tails) (format #t "rest: ~a ~%" rest)))) ⇒ heads: ((a.0 b.0 c.0) (a.1 b.1 c.1)) tails: ((1.0 2.0 3.0) (1.1 2.1 3.1)) rest: ((x.0 y.0 z.0) (x.1 y.1 z.1)) I imagine the element names could be improved, and perhaps the data structure simplified while preserving the lesson that it contains, but I can't think of how that would be done, but I'm open to suggestions. But overall, I think showing nested patterns first off, followed by simpler examples that ellucidate previous ones, is preferable so as to cater both to the "reference" user as well as the total beginner. ^ permalink raw reply [flat|nested] 8+ messages in thread
end of thread, other threads:[~2023-02-03 14:14 UTC | newest] Thread overview: 8+ messages (download: mbox.gz / follow: Atom feed) -- links below jump to the message on this page -- 2023-02-01 13:09 [PATCH v3] docs/match: pattern matcher example makeover Blake Shaw 2023-02-01 16:40 ` Maxime Devos 2023-02-02 17:47 ` David Pirotte 2023-02-03 10:05 ` Blake Shaw 2023-02-03 12:05 ` Arun Isaac 2023-02-03 13:10 ` Blake Shaw 2023-02-03 13:43 ` Josselin Poiret 2023-02-03 14:14 ` Blake Shaw
This is a public inbox, see mirroring instructions for how to clone and mirror all data and code used for this inbox; as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).