[racket-users] Re: code reflection
On Mon, 16 Oct 2017 17:11:53 -0400, Matthias Felleisenwrote: >> On Oct 16, 2017, at 2:17 PM, George Neuner wrote: > >> Lisp's macros are ... I won't say easier to use correctly, because >> they aren't ... but IMO they are easier to understand and think about >> because the input is just a tree of normal Lisp objects that can be >> manipulated using normal Lisp functions. No dichotemy of "this is >> syntax" vs "that is data", and no bridges to cross between two >> different representations. > > >Lisp macros are easier than Racket’s in the same way that it >was so much easier to write procedures in ASM than in Pascal. >It was so much easier to manipulate bit patterns directly, >why bother calling some Integer and others Chars. I think maybe you overlooked where I said "use correctly". Non-hygienic macros are much easier to write ... but they are also much easier to f_ up and do something unexpected when used. In any case, it was not my intent to start a religious war here. Scheme's macros work as they do for a reason [and yes! I do know the reason]. I don't necessarily have to like it though, nor do I have to say that I do. I have read "Fear of Macros". Long ago in fact. I don't fear macros at all ... I'm a [getting old] compiler geek and what I fear is to lack understanding of what's going on under the hood. Lisp is pretty transparent about it's IR internals ... Scheme not so much. George -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
On Tue, Oct 17, 2017 at 2:36 AM, Konrad Hinsenwrote: > I never got > around to work on this seriously, both because of the 24-hour-per-day limit > that I cannot seem to get rid of, When you figure that one out, don't forget to open source your solution, okay? Or, at the very least, patent it and sell licenses. I'll be first in line. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
> Greg, if you're reading this...any chance you might expand FoM? In the meantime, the "Syntax Parse Examples" package is always accepting contributions: http://docs.racket-lang.org/syntax-parse-example/index.html#%28part._.The_.Examples%29 -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
> On Oct 16, 2017, at 5:26 PM, David Storrswrote: > > On Mon, Oct 16, 2017 at 5:11 PM, Matthias Felleisen > wrote: >> >> You speak of personal failing, and I think that’s incorrect. >> We are missing a good introduction to syntax and friends. > > George Neuner, you might want to look at Greg Hendershott's "Fear of > Macros" (http://www.greghendershott.com/fear-of-macros/). I found it > to be a very good introduction to syntax and to macros in general. I > just wish he hadn't stopped before covering syntax-parse, as I did not > find the reference docs as helpful for that one as he did. > > Greg, if you're reading this...any chance you might expand FoM? Thanks, I am well aware of Greg’s work even though I forget it too often. My introductory lectures to the macro system at the Racket summer school ended up following the same path (w/o remembering Greg’s write-up). @ George: Yes, do read Greg’s post. And you may find the lectures Matthew and I gave at the Racket school useful too. Still, we ought to be able to do much better — Matthias -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
On Mon, Oct 16, 2017 at 5:11 PM, Matthias Felleisenwrote: > > You speak of personal failing, and I think that’s incorrect. > We are missing a good introduction to syntax and friends. George Neuner, you might want to look at Greg Hendershott's "Fear of Macros" (http://www.greghendershott.com/fear-of-macros/). I found it to be a very good introduction to syntax and to macros in general. I just wish he hadn't stopped before covering syntax-parse, as I did not find the reference docs as helpful for that one as he did. Greg, if you're reading this...any chance you might expand FoM? Dave > > But having said that, I need to contradict this one: > >> Lisp's macros are ... I won't say easier to use correctly, because >> they aren't ... but IMO they are easier to understand and think about >> because the input is just a tree of normal Lisp objects that can be >> manipulated using normal Lisp functions. No dichotemy of "this is >> syntax" vs "that is data", and no bridges to cross between two >> different representations. > > > Lisp macros are easier than Racket’s in the same way that it > was so much easier to write procedures in ASM than in Pascal. > It was so much easier to manipulate bit patterns directly, > why bother calling some Integer and others Chars. > > — Matthias > > > > -- > You received this message because you are subscribed to the Google Groups > "Racket Users" group. > To unsubscribe from this group and stop receiving emails from it, send an > email to racket-users+unsubscr...@googlegroups.com. > For more options, visit https://groups.google.com/d/optout. -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
> On Oct 16, 2017, at 2:17 PM, George Neunerwrote: You speak of personal failing, and I think that’s incorrect. We are missing a good introduction to syntax and friends. But having said that, I need to contradict this one: > Lisp's macros are ... I won't say easier to use correctly, because > they aren't ... but IMO they are easier to understand and think about > because the input is just a tree of normal Lisp objects that can be > manipulated using normal Lisp functions. No dichotemy of "this is > syntax" vs "that is data", and no bridges to cross between two > different representations. Lisp macros are easier than Racket’s in the same way that it was so much easier to write procedures in ASM than in Pascal. It was so much easier to manipulate bit patterns directly, why bother calling some Integer and others Chars. — Matthias -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
[racket-users] Re: code reflection
Hi all, Thanks to everyone who replied. I apologize for posting and vanishing ... a family emergency on Saturday took me away for the weekend. David: Thank you for the exposition about your task scheduler. I have something similar, a (thread safe) priority queue based on data/heap, although it isn't persistent like yours. This "time definition" code was an attempt to make using it easier / more natural. Ryan: You are probably correct that this job doesn't need such a complex macro ... probably one to parse out times, plus some purpose functions would suffice. But the question about how to reference the environment from a macro is tangential to the task and I really thank you for trying to answer it. I'm going to have to study your solution for a while as I'm not sure I yet understand it. - I have written 2 compilers from scratch, and I have no trouble using non-hygienic macros in Lisp, or syntax-rules, syntax-case in Scheme. But I start to get queasy when the task exceeds what can be done easily with syntax-rules or syntax-case. I can't easily articulate my problem with syntax: the best I can come up with is that it is, in a real sense, a black box. There is an API to manipulate it ... but no real understanding of exactly what is being manipulated or how the functions tie together. For me the documentation talks a lot but doesn't seem to say very much. [I recognize that this is a personal failing ... obviously other people are able to figure it out, so the problem must be with me.] Lisp's macros are ... I won't say easier to use correctly, because they aren't ... but IMO they are easier to understand and think about because the input is just a tree of normal Lisp objects that can be manipulated using normal Lisp functions. No dichotemy of "this is syntax" vs "that is data", and no bridges to cross between two different representations. Thanks to everybody. I need to go now and study what Ryan did. George -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.
Re: [racket-users] Re: code reflection
Given the quality and depth of prior comments in this thread, I'm a little reluctant to chime in. I suppose, however, that people on this list tend to be forgiving and maybe my much less elegant solution will be helpful, at least as inspiration. This solution is not originally intended for the problem you're asking about, but it can be used for your problem. Effectively, you want to carry state forward in time from one function to another. I had a somewhat related problem in that I wanted to schedule tasks for the future. Obviously, I needed a job queue. Fortunately, Racket has one: https://docs.racket-lang.org/job-queue/index.html (Hat tip to Jay McCarthy) I wanted to be able to schedule them for the future. One way to do that would be to make up a job like this: (thunk (sleep 60) (do stuff)) ...and then hand it to the worker queue. It would run in its own thread so as not to block the main thread, so scheduling issue solved. The problem with this is that the jobs are not durable; if the Racket process crashed then all the jobs would go up in a puff of magic smoke with no way to recover them. Ah, durability, you say? Isn't that what the 'D' in DB stands for? (Answer: no, it's not.) Okay, database sounds good. So, I created a 'tasks' table that would store details about the job that I wanted to run and when to run them. Periodically a thread would check the table, grab a job that was eligible, and run it. It would have been nice to be able to shove straight Racket code in there, but that would have required 'evil' -- I mean, 'eval'. Instead, I stuffed in a JSON blob that looked (in part) like this: { "task" : "sync-file-list-for-team", "args" : [ "team name"] } Then I did the ugly part: I created a global variable. Said variable was a hash table (yes, yes, John, I know: structs are better than hashes for most things. You convinced me in the general case, but for this I really did need something free-form) that looked like so: (hash "sync-file-list" (lambda (args-list) ...stuff...)) You can recover the function by doing: (define task (...database query that retrieves the JSON object and parses it into a hash...)) ; 'task' is something like (hash "task" "sync-file-list "args" (list "team name")) (define func (function-from task)) ; take the value of the "task" key, look it up in the global jumptable, retrieve the lambda stored there (define args (args-from task)) ; get the list from the task hash (func args) ; run it Anything that wants to schedule a task can now do this: (require "lib/workers.rkt") (define/contract (sync-file-list-for-team lst) (-> list? any) ...do stuff...) (register-functions sync-file-list-for-team) (add-task db-connection #:task sync-file-list-for-team #:args (list "team name") #:delay 90 ; will not run for *at least* 90 seconds. No guarantee about when it *will* run #:priority 10 ; default is 0, bigger is higher priority ) Or, if you'd prefer to be able to pass fancier state: (register-functions (list "extra-args-for-a-func" (thunk (list 'x 'y (thunk 7) (define (a-func lst) (define extra-args ((function-from (hash "task" "extra-args-for-a-thunk" ; extra-args is now (list 'x 'y (thunk 7)) ...) The tasks are now durable, since they're in the DB. If the Racket process crashes then the jumptable will go away, but as long as your register-functions calls are at top level then the jumptable will be rebuilt when the module is next loaded. Like I said, this isn't exactly the problem you were asking about -- there's no macros involved, for one thing -- and it's way less elegant than the other solutions that people have been offering. Despite that, I've found it useful and so I thought I'd share. On Sat, Oct 14, 2017 at 7:37 PM, 'John Clements' via Racket Userswrote: > Lovely! I was thinking along these lines, but you hit it out of the park. > Sounds like you must be avoiding some really important task! > > John > >> On Oct 14, 2017, at 11:31, Ryan Culpepper wrote: >> >> On 10/14/2017 05:01 AM, George Neuner wrote: >>> On 10/14/2017 3:00 AM, Jack Firth wrote: So is there a way ... from normal code ... to get at the locals of functions higher in the call chain? Or at least the immediate caller? Some reflective capability that I haven't yet discovered? I'm not sure if there's a way to do that, but I'm wondering if what you want to do can be achieved more simply with plain functions and a very small macro wrapper. In particular, I suspect putting too much logic in the macro is what led you to eval which is the root of all evil. From what I can tell there shouldn't be any need at all for eval or any sort of dynamic runtime compilation to do things like what you're describing. Could you give a few more details about your use case? Ideally with some
Re: [racket-users] Re: code reflection
Lovely! I was thinking along these lines, but you hit it out of the park. Sounds like you must be avoiding some really important task! John > On Oct 14, 2017, at 11:31, Ryan Culpepperwrote: > > On 10/14/2017 05:01 AM, George Neuner wrote: >> On 10/14/2017 3:00 AM, Jack Firth wrote: >>> >>>So is there a way ... from normal code ... to get at the locals of >>>functions higher in the call chain? Or at least the immediate >>>caller? >>>Some reflective capability that I haven't yet discovered? >>> >>> >>> I'm not sure if there's a way to do that, but I'm wondering if what you >>> want to do can be achieved more simply with plain functions and a very >>> small macro wrapper. In particular, I suspect putting too much logic in the >>> macro is what led you to eval which is the root of all evil. From what I >>> can tell there shouldn't be any need at all for eval or any sort of dynamic >>> runtime compilation to do things like what you're describing. Could you >>> give a few more details about your use case? Ideally with some example code >>> illustrating the problem? >> Basically, this is a sort of Unix at-like function for flexible scheduling. >> It takes an expression containing dates, times, certain keywords and >> arbitrary numeric expressions, and it produces seconds since the epoch. >> Code is attached - hope it survives posting to the list. It should be >> runnable as is. >> What led me to eval originally was wanting to reference arbitrary >> functions/variables from the runtime environment. I'd like to be able to say >> things like: >>(let [(y 42)] (schedule (at now + y mins) ...)) >> and similar involving top-level defined functions [which already works with >> eval]. >> I know that eval is not needed if I generate inline code rather than having >> the macro invoke a normal function. But that is complicated by having to >> deal with free form expressions: they can have internal references between >> things which are not necessarily adjacent. It is doable, but at some >> expense and not very cleanly. >> I started out going the "compile" route - just generating inline code. But >> as more functionality was added, that became unwieldy. So I switched to a >> runtime function. Right now the assoc list code is overly complex [so please >> ignore it] - it is there as a debugging tool until I get everything working >> exactly right. > > Your example above shows that you want the `at` macro to accept a mixture of > Racket expressions and syntax that you interpret. If you want to accept > Racket expressions, you must leave them as syntax objects. Once you flatten > them with `syntax->datum`, there is *no way* to *correctly* recover their > meaning. > > Here's my attempt to adapt the HtDP design recipe to a scaled-down version of > this problem. > > First, let's think about the syntax (grammar) that `at` should accept. Let's > call it a Time. What are some reasonable Times? > > now > now + 30 ;; means 30 seconds > today > tomorrow + 20 mins > now + x mins + (/ ms 1000) secs > "2017-10-14 1:17:25" + 30 mins > > But there are also some terms that are nonsense as Times: > > 12 > today + tomorrow > now mins > > Let's say (as a place to start) that a Time consists of an absolute reference > point (like now or today) and some number of offsets. Here's a grammar: > > ;; Time = TimeBase {+ TimeExt}* > ;; TimeBase = now | today | String[Date] > ;; TimeExt = Expr[Real] MaybeUnit > ;; MaybeUnit = | secs | mins > > Let's call the *meaning* of a Time a TimeVal, and let's represent it as a > real number of seconds, using the same epoch as (current-seconds). > > ;; A TimeVal is a real number of seconds > ;; using the same epoch as (current-seconds) > > The meaning of TimeBase is also a TimeVal. The meanings of TimeExt and > MaybeUnit are both Real (they are duration and durations scales; they have no > epoch). > > Now to translate that plan to Racket. > > First, turn keywords like `now` into "illegal use" macro definitions: > > (begin-for-syntax >(define (at-keyword stx) > (raise-syntax-error #f "illegal use of `at` keyword" stx))) > (define-syntax now at-keyword) > (define-syntax today at-keyword) > (define-syntax secs at-keyword) > (define-syntax mins at-keyword) > > Then define syntax classes for the nonterminals in the grammar. I'll also > define an `expr` attribute to compute the meaning of each term. For example, > the syntax class for TimeBase is > > (begin-for-syntax >(define-syntax-class TimeBase > #:attributes (expr) ;; expression of TimeVal > #:literals (now today) > (pattern now > #:with expr #'(current-seconds)) > (pattern today > #:with expr #'(today-fun)) > (pattern s:str > #:with expr #'(parse-date-string s > > To avoid complicating the syntax class and also to avoid making the expansion > bigger than
Re: [racket-users] Re: code reflection
On 10/14/2017 05:01 AM, George Neuner wrote: On 10/14/2017 3:00 AM, Jack Firth wrote: So is there a way ... from normal code ... to get at the locals of functions higher in the call chain? Or at least the immediate caller? Some reflective capability that I haven't yet discovered? I'm not sure if there's a way to do that, but I'm wondering if what you want to do can be achieved more simply with plain functions and a very small macro wrapper. In particular, I suspect putting too much logic in the macro is what led you to eval which is the root of all evil. From what I can tell there shouldn't be any need at all for eval or any sort of dynamic runtime compilation to do things like what you're describing. Could you give a few more details about your use case? Ideally with some example code illustrating the problem? Basically, this is a sort of Unix at-like function for flexible scheduling. It takes an expression containing dates, times, certain keywords and arbitrary numeric expressions, and it produces seconds since the epoch. Code is attached - hope it survives posting to the list. It should be runnable as is. What led me to eval originally was wanting to reference arbitrary functions/variables from the runtime environment. I'd like to be able to say things like: (let [(y 42)] (schedule (at now + y mins) ...)) and similar involving top-level defined functions [which already works with eval]. I know that eval is not needed if I generate inline code rather than having the macro invoke a normal function. But that is complicated by having to deal with free form expressions: they can have internal references between things which are not necessarily adjacent. It is doable, but at some expense and not very cleanly. I started out going the "compile" route - just generating inline code. But as more functionality was added, that became unwieldy. So I switched to a runtime function. Right now the assoc list code is overly complex [so please ignore it] - it is there as a debugging tool until I get everything working exactly right. Your example above shows that you want the `at` macro to accept a mixture of Racket expressions and syntax that you interpret. If you want to accept Racket expressions, you must leave them as syntax objects. Once you flatten them with `syntax->datum`, there is *no way* to *correctly* recover their meaning. Here's my attempt to adapt the HtDP design recipe to a scaled-down version of this problem. First, let's think about the syntax (grammar) that `at` should accept. Let's call it a Time. What are some reasonable Times? now now + 30 ;; means 30 seconds today tomorrow + 20 mins now + x mins + (/ ms 1000) secs "2017-10-14 1:17:25" + 30 mins But there are also some terms that are nonsense as Times: 12 today + tomorrow now mins Let's say (as a place to start) that a Time consists of an absolute reference point (like now or today) and some number of offsets. Here's a grammar: ;; Time = TimeBase {+ TimeExt}* ;; TimeBase = now | today | String[Date] ;; TimeExt = Expr[Real] MaybeUnit ;; MaybeUnit = | secs | mins Let's call the *meaning* of a Time a TimeVal, and let's represent it as a real number of seconds, using the same epoch as (current-seconds). ;; A TimeVal is a real number of seconds ;; using the same epoch as (current-seconds) The meaning of TimeBase is also a TimeVal. The meanings of TimeExt and MaybeUnit are both Real (they are duration and durations scales; they have no epoch). Now to translate that plan to Racket. First, turn keywords like `now` into "illegal use" macro definitions: (begin-for-syntax (define (at-keyword stx) (raise-syntax-error #f "illegal use of `at` keyword" stx))) (define-syntax now at-keyword) (define-syntax today at-keyword) (define-syntax secs at-keyword) (define-syntax mins at-keyword) Then define syntax classes for the nonterminals in the grammar. I'll also define an `expr` attribute to compute the meaning of each term. For example, the syntax class for TimeBase is (begin-for-syntax (define-syntax-class TimeBase #:attributes (expr) ;; expression of TimeVal #:literals (now today) (pattern now #:with expr #'(current-seconds)) (pattern today #:with expr #'(today-fun)) (pattern s:str #:with expr #'(parse-date-string s To avoid complicating the syntax class and also to avoid making the expansion bigger than necessary, factor run-time behavior out into helper functions, like `today-fun`: ;; today-fun : -> TimeVal (define (today-fun) (define today (current-date)) (find-seconds 0 0 0 (date-day today) (date-month today) (date-year today))) Here's the syntax class for TimeExt. It uses `expr/c` to make sure the given Racket expression actually produces a number. (begin-for-syntax
Re: [racket-users] Re: code reflection
On 10/14/2017 3:00 AM, Jack Firth wrote: So is there a way ... from normal code ... to get at the locals of functions higher in the call chain? Or at least the immediate caller? Some reflective capability that I haven't yet discovered? I'm not sure if there's a way to do that, but I'm wondering if what you want to do can be achieved more simply with plain functions and a very small macro wrapper. In particular, I suspect putting too much logic in the macro is what led you to eval which is the root of all evil. From what I can tell there shouldn't be any need at all for eval or any sort of dynamic runtime compilation to do things like what you're describing. Could you give a few more details about your use case? Ideally with some example code illustrating the problem? Basically, this is a sort of Unix at-like function for flexible scheduling. It takes an expression containing dates, times, certain keywords and arbitrary numeric expressions, and it produces seconds since the epoch. Code is attached - hope it survives posting to the list. It should be runnable as is. What led me to eval originally was wanting to reference arbitrary functions/variables from the runtime environment. I'd like to be able to say things like: (let [(y 42)] (schedule (at now + y mins) ...)) and similar involving top-level defined functions [which already works with eval]. I know that eval is not needed if I generate inline code rather than having the macro invoke a normal function. But that is complicated by having to deal with free form expressions: they can have internal references between things which are not necessarily adjacent. It is doable, but at some expense and not very cleanly. I started out going the "compile" route - just generating inline code. But as more functionality was added, that became unwieldy. So I switched to a runtime function. Right now the assoc list code is overly complex [so please ignore it] - it is there as a debugging tool until I get everything working exactly right. George -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout. #lang racket/base (require (for-syntax racket/base racket/format racket/pretty ) racket/base racket/match racket/list racket/format racket/dict racket/date racket/port ) (provide at at-time month/year ) ;%%% #| allows free form date/time descriptions in the style of the Unix "at" command. converts the description into seconds since the Unix epoch: 1970-01-01 at 00:00hrs. the resulting seconds value may be converted into a usable date structure, or passed to an alarm event for scheduling. freestanding + operators will be ignored. for signed values use [+-]?[:digits:]+. key words: "now" = current date/time "today" - at 00hrs "tomorrow" - at 00hrs "this-month" - at 00hrs on the 1st "next-month" - at 00hrs on the 1st dates must be entered in -mm-dd format. times are 24hr format: hours and minutes are required - seconds are optional. a separate AM/PM qualifier can be used to adjust ambiguous times. anything not a date, time or keyword will be evaluated as a racket expression. expressions may reference variables or functions from the surrounding code environment. an expression may be followed immediately by a unit multiplier. units may be seconds, minutes, hours, days, or weeks (or some of the typical abbreviations for these units). |# ;%%% ;== ; ; macro interface - generates call to ; parsing function at runtime ; ;== (define-syntax (at stx) (let* [ (input (cdr (syntax->datum stx))) (input (map ~a input)) (fnspec (list 'apply '+ (list* 'at-time input))) ] (datum->syntax stx fnspec) )) ;%%% ;== ; ; parse a time/date description ; ;== (define (at-time #:namespace [ns (current-namespace)] . input) (let* [ (now(current-seconds)) (today (current-date)) (hour12 (* 12 60 60)) (hour13 (* 13 60 60)) ] (let loop [ (input input) (output '()) ] (match input ; done ([? empty?] (eprintf "=> ~s~n" (reverse output)) (map cdr output) ) ; current time ([list "now" _ ___ ] (loop (cdr input) (cons (cons 'now now) output))) ; date (at 00:00:00) ([list (pregexp px-date [list _
[racket-users] Re: code reflection
> > So is there a way ... from normal code ... to get at the locals of > functions higher in the call chain? Or at least the immediate caller? > Some reflective capability that I haven't yet discovered? > I'm not sure if there's a way to do that, but I'm wondering if what you want to do can be achieved more simply with plain functions and a very small macro wrapper. In particular, I suspect putting too much logic in the macro is what led you to eval which is the root of all evil. From what I can tell there shouldn't be any need at all for eval or any sort of dynamic runtime compilation to do things like what you're describing. Could you give a few more details about your use case? Ideally with some example code illustrating the problem? -- You received this message because you are subscribed to the Google Groups "Racket Users" group. To unsubscribe from this group and stop receiving emails from it, send an email to racket-users+unsubscr...@googlegroups.com. For more options, visit https://groups.google.com/d/optout.