phanthero
2020-12-23 12:50:54

Hmm, I didn’t think this would be that much of an abstraction, since Racket already has been implemented in C (I wasn’t involved with the BC implementation, but I was assuming you might have been since you expressed some familiary with terms like Sheme_Object (a type used in the BC implementation))?

Either way, yeah, it makes sense that some types in Racket need to be dereferenced, and then mutated, such as ports, mutable strings, hash tables, vectors, mcons, etc, so it wouldn’t make sense if we said that no dereferencing happens either.

What I gather from all this is that the abstract “dereferencing” happens (although I wonder if this actually happens in BC?), but there are measures in place to make sure that the type information never changes, and also that functions like (define (foo x) ...) do not retain the address of whatever value was used to called it like (define (main a b c) ... (foo a) ...) , here foo does not know the location of a, but only knows the location of its own copy (by value) of a. Even though a might be a number and foo wants to set a to be another number (so, type is safe?) it cannot do so, right?

Let me know if my understanding is correct. Still not completely certain if this really is an allegory or what actually happens in BC


capfredf
2020-12-23 15:06:13

Sadly, I don’t think Typed Racket supports mutable sets


rokitna
2020-12-23 20:29:07

I think that’s right. Inside foo, (set! x 2) only changes which (racket_value *) the variable x is bound to. It doesn’t have any way to change the binding of the variable a, nor does it have any general way to read or write to the data the (racket_value *) points to. Although it has no general way, it has many specific ways to read or write certain parts of the data, using operations like mcons?, mcar, and set-mcar!.

Sorry if this metaphor suggested there were more general-purpose dereferencing operations available. I picked this hypothetical implementation technique so that the distinction you were asking about (passing by reference or not) had some plausible interpretation. If people thought about Racket variables this way, then it would form a basis of consensus for how “pass by reference” and “pass by value” features should behave if they existed in Racket… but those features don’t exist today, at least not in safe Racket code. So, since you’re trying to understand Racket in terms of this model, I should be more clear that not all the capabilities of the model show up in the capabilities of the language it implements.

I’m afraid I’ve barely even looked at the actual implementation used by Racket. This is at best a naïve approximation, and I don’t think implementations of Racket even have to resemble this technique.


phanthero
2020-12-23 20:59:39

Thanks for the great explanation!

PS. I like the diacritic used in “naïve” (I have no idea how to input diacritics with my US English Keyboard LOL)


rokitna
2020-12-23 21:02:31

lol, this time it was easier ’cause I’m on mobile and can long-press to get diacritics :-p


phanthero
2020-12-23 21:56:59

So I guess in the example, even though x is local to foo, if x is a mutable type (like mcons), there is actually some link maintainted to a… That is interesting indeed


rokitna
2020-12-24 01:37:10

I suppose so. The mutable cons cell is somewhere in memory, and the address of that memory is what’s stored in x and a . Neither variable x nor variable a carries any information about the other, but they are related by pointing to the same place. Or in Racket terms, they’re related by being two different variables that are bound to the same value.


yuhsien77
2020-12-24 01:51:43

Hello everyone! I’m looking for some help with a macro. I am stumped with all the hygienic/unhygienic distinctions and the syntax thing.

I’m trying to write a macro that creates a bunch of functions to extract the n-th item from a list, from 1 to n (just for practice). nths-1 is working fine, but it doesn’t seem right to me: I just took an Emacs Lisp macro that I would write, and added syntax-&gt;datum and datum-&gt;syntax until it worked. nths-2 is my attempt at idiomatic Racket/Scheme-style macros, but it isn’t working. The error I’m getting is: ; define: not allowed in an expression context, even though I don’t see that I’m defining anything yet, just passing the syntax. I’d be glad for any help with this! Racket macros hurt my brain! Thank you! (begin-for-syntax (define (nths-helper upto) (for/list ((n (in-range 1 (add1 upto)))) (define f (string-&gt;symbol (format "~ath" n))) `(define (,f lst) (list-ref lst ,n))))) (define-syntax (nths-1 stx) (syntax-case stx () ((_ i) (let ((fs (nths-helper (syntax-&gt;datum #'i)))) (datum-&gt;syntax #'i `(begin ,@fs)))))) (define-syntax (nths-2 stx) (syntax-case stx () ((_ i) (with-syntax ((fs (nths-helper (syntax-&gt;datum #'i)))) #'(begin fs))))) (As an aside, where can I find more macro exercises? I found <https://lexi-lambda.github.io/racket-macro-exercises/index.html|lexi-lambda’s exercises> and I’m currently stuck on part 3.)


greg
2020-12-24 03:04:58

@yuhsien77 I’m guessing you struggling with a few things (at least I did). - You mentioned hygiene. But more so, in your example: - Lisp macros work on quoted lists. Racket macros work on syntax objects. - Racket macros usually work in a pattern matching style, and you supply a template that uses some of the things you match. (You can also do a quasisyntax style, like quasiquote with Lisp macros.) When I was getting started with Racket macros I wrote this, I don’t know if you might find it helpful: https://www.greghendershott.com/fear-of-macros/all.html


sorawee
2020-12-24 03:07:06

Here’s how I would write it:

#lang racket (require (for-syntax racket/syntax)) (define-syntax (nths stx) (syntax-case stx () [(_ upto) #`(begin #,@(for/list ([n (in-range 1 (add1 (syntax-e #'upto)))]) (with-syntax ([the-id (format-id stx "~ath" n)]) #`(define (the-id lst) (list-ref lst #,n)))))])) (nths 10) (1th '(1 2 3))


greg
2020-12-24 03:08:23

Yeah I was going to say the quasisyntax approach might be the shortest path from Lisp macros. Although, the similarity might cause mis-assumptions? But still, that’s a great example.


yuhsien77
2020-12-24 03:09:54

@greg @sorawee Thank you for your answers! I’ve actually been going through your tutorial, Greg (thank you!), I guess it just takes time for these things to sink in.


sorawee
2020-12-24 03:10:11

One more thing that I want to point out is that, this is not a simple macro. It is intrinsically unhygienic (due to the use of format-id, which under the hood is datum-&gt;syntax). So the problem that you are trying to solve is fairly advanced.


yuhsien77
2020-12-24 03:10:48

I am aware of the distinction between syntax and datum, but on a theoretical level. In practical use I trip up a lot.


yuhsien77
2020-12-24 03:12:11

@sorawee Yes, as far as I understand this problem requires an unhygienic macro (since I’m creating new functions at the call site), and these are hard in Racket.


yuhsien77
2020-12-24 03:13:23

@sorawee Damn, that nested quasisyntax is so meta.


yuhsien77
2020-12-24 03:14:39

While we’re here, I have a question about syntax-case. Is it deprecated? I get the impression that syntax-parse is the way to go, and it basically provides a superset of the functionality of syntax-case.


greg
2020-12-24 03:15:29

It’s a bit dense because the one macro is trying to do two things at once, the way you’d wanted. Maybe try writing a “definer” macro that defines a single function? Then you can see that things like format-id are doing.

And when you’re comfortable, you could write a define-many-at-once macro?


yuhsien77
2020-12-24 03:16:15

@greg I did write this first:

(define-syntax (nth stx) (syntax-case stx () ((_ i) (with-syntax ((f (format-id #'i "~ath" (syntax-&gt;datum #'i)))) #'(define (f lst) (list-ref lst i))))))


greg
2020-12-24 03:16:28

Actually that’s one tip I have, when writing macros. Is be very step by step. Add one wrinkle at a time. More so than with “normal” programming. When I try to go from A to Z all at once, I often get confused.


yuhsien77
2020-12-24 03:16:54

Yeah, I definitely feel that.


yuhsien77
2020-12-24 03:17:55

My problem is, I don’t really know what macros to write. I mentioned Alexis King’s exercises above, and I’ve done the threading bits, but now I’m up to part 3 and unsure how to solve it.


yuhsien77
2020-12-24 03:19:17

I also have had zero ideas so far for macros of my own (well, one: the nths macro above). I just don’t get sudden inspirations for macros, cause I’m so used to living without them.


yuhsien77
2020-12-24 03:19:46

(Also, so many of the macro examples in various tutorials don’t even need to be macros…)


sorawee
2020-12-24 03:20:45

Somewhat. I personally always use syntax-parse because it’s so much nicer


sorawee
2020-12-24 03:21:01

But for learning, some people might find these additional features distracting


yuhsien77
2020-12-24 03:21:22

They’re optional though, aren’t they? Hidden away in kwargs.


greg
2020-12-24 03:21:42

I don’t know if this will be “too much” for you now. Also it’s particularly focused on syntax-parse, which is the most modern/shiny way of writing macros that produce good error messages among other nice things. But: https://docs.racket-lang.org/syntax-parse-example/index.html


yuhsien77
2020-12-24 03:21:47

Does the same apply to syntax-rules? I’m guessing yes


yuhsien77
2020-12-24 03:24:19

Thanks! I don’t see why I shouldn’t just jump straight into syntax-parse, if that’s what people are actually using. I already used it for the threading macro exercise, since it supports two (...)’s in one sexp, unlike syntax-case.


yuhsien77
2020-12-24 03:25:35

I don’t think it’s any more difficult to use than syntax-case, but it does appear vastly more featureful.


greg
2020-12-24 03:25:58

Oh, yes, I think good/fine to start with syntax-parse. I just didn’t know if the examples might be too esoteric or intricate, and induce mind-melt. :slightly_smiling_face:


yuhsien77
2020-12-24 03:26:21

I’m already there :slightly_smiling_face:


greg
2020-12-24 03:26:24

But you never know. Sometimes I read things I’m not quite ready for, but some of it sinks in, and later I go back.


yuhsien77
2020-12-24 03:26:42

Hehe. Can you say “monad” :wink:


greg
2020-12-24 03:26:46

I call this advanced learning technique “squinty reading”. :slightly_smiling_face:


yuhsien77
2020-12-24 03:27:27

This does remind me of learning Haskell. I know by now not to rush it, and give these things time.


greg
2020-12-24 03:28:45

I think it’s totally normal to need a lot of time for this to sink in. If someone feels they got it right away, it probably means they just don’t know what they don’t know, yet. But you can get comfortable and do some really useful/fun macrology in like, less than a decade, I promise. :slightly_smiling_face:


yuhsien77
2020-12-24 03:29:59

Haha, that’s reassuring!


greg
2020-12-24 03:31:15

Oh also, I should point out define-simple-macro. For a lot of small tasks, day-to-day boilerplate-nukers etc. that can be great. It works for things where don’t need to e.g. synthesize new identifiers using format-id, for example.


yuhsien77
2020-12-24 03:31:19

Would you recommend “On Lisp” for Racketeers? I understand it’s chock full of unhygienic macros, so might not be the best choice. It’s just that I prefer learning by doing, and I’d like to find a bunch of macro exercises to build up my understanding of them.


greg
2020-12-24 03:32:39

I only skimmed On Lisp and it was many years ago, so I don’t know. But I think things like that and Let Over Lambda are probably some mix of useful and “misleading” for Racket, both.


yuhsien77
2020-12-24 03:35:23

Ok, I’ll keep at it. Thank you @greg and @sorawee for your help!


greg
2020-12-24 03:55:46

By the way, I liked @sorawee’s example above, a lot. If you find it too twisty, one non-quasiquote way to write it could be: #lang racket/base (require (for-syntax racket/base racket/syntax) syntax/parse/define) (define-syntax-parser define-nths [(_ thru) #:with (id ...) (for/list ([n (in-range (add1 (syntax-e #'thru)))]) (format-id this-syntax "~ath" n)) #:with (n ...) (for/list ([i (in-range (add1 (syntax-e #'thru)))]) (datum-&gt;syntax this-syntax i)) #'(begin (define (id xs) (list-ref xs n)) ...)]) The #:with bits are defining some lists of pieces of syntax. (They are basically the same as using with-syntax). The ... in the template “unrolls” those. So, I mean it’s the usual trade-off with any function: Introducing some intermediate variables to name values takes more lines, but make the pieces clearer.

Having said all that I would probably write it using quasisyntax, if the future audience were mainly me, and I didn’t expect the macro to grow more complicated.


greg
2020-12-24 03:58:00

define-syntax-parser is just a way of not needing to write out (define-syntax (foo stx) (syntax-parse __). It’s an indentation remover. :slightly_smiling_face:


sorawee
2020-12-24 03:58:08

Let me throw in more syntax-parse features :)

#lang racket/base (require (for-syntax racket/base racket/syntax) syntax/parse/define) (define-simple-macro (define-nths thru:number) #:with (id ...) (for/list ([n (in-range (add1 (syntax-e #'thru)))]) (format-id this-syntax "~ath" n)) #:with (n ...) (for/list ([i (in-range (add1 (syntax-e #'thru)))]) (datum-&gt;syntax this-syntax i)) (begin (define (id xs) (list-ref xs n)) ...)) (define-nths 10) (1th '(1 2 3))


greg
2020-12-24 03:58:47

Oh ha ha. Now I learn that #:with works with define-simple-macro, which I never knew.


greg
2020-12-24 03:59:03

Nice! Thanks. :smile:


sorawee
2020-12-24 03:59:21

Initially it doesn’t work I think. Then there’s a PR to make it work.


greg
2020-12-24 04:00:14

Now I feel a little better.


greg
2020-12-24 04:01:00

Also when I’m working on Racket Mode, which is supposed to work with somewhat old Rackets, I’m often deliberately not trying to use the new shiny things.


greg
2020-12-24 04:01:19

But this may have been in there a long time and I just never noticed. Glad to know about it!


samth
2020-12-24 04:09:20

I once said (to Dan Friedman, which made it more significant) “friends don’t let friends use syntax-rules”


yuhsien77
2020-12-24 04:45:38

@greg @sorawee Very cool! I take it the #:with is from here? https://docs.racket-lang.org/syntax/stxparse-specifying.html#%28tech._pattern._directive%29


sorawee
2020-12-24 04:45:56

Yep


yuhsien77
2020-12-24 04:46:10

Is there a way to use ... with the quasisyntax version?


sorawee
2020-12-24 04:53:07

What do you mean by that? ... works inside quasisyntax, if that’s what you are asking. But if you mean something else, the answer is likely no.


yuhsien77
2020-12-24 04:55:18

This is very impressive! And more readable IMO.


yuhsien77
2020-12-24 04:56:37

I see you’re not requiring (for-syntax syntax/parse) though? If I omit it, Racket complains.


sorawee
2020-12-24 04:56:57

He requires syntax/parse/define instead.


sorawee
2020-12-24 04:57:19

syntax/parse/define automatically requires (for-syntax syntax/parse)


yuhsien77
2020-12-24 04:57:27

Oh I see!


yuhsien77
2020-12-24 05:00:42

I guess the way to write it with ... would be what @greg and yourself wrote later on. I’ll read these a few more times.