guile-user
[Top][All Lists]
Advanced

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

Re: Pure (side-effect-free) calls into c/c++?


From: Taylan Kammer
Subject: Re: Pure (side-effect-free) calls into c/c++?
Date: Sat, 11 Jan 2020 22:56:00 +0100
User-agent: Mozilla/5.0 (Windows NT 10.0; WOW64; rv:68.0) Gecko/20100101 Thunderbird/68.3.1

On 11.01.2020 19:52, Linas Vepstas wrote:
> Or, thinking aloud a bit: boxes and symbols....
> 
> So, for example, if I was able to tell apart calls (f 42) from calls (f x)
> where x is a "symbol" (or "variable", *see below*) referencing an integer,
> then, for the former case, I could create a new symbol (in guile) and
> attach it to a box, fill that box with whatever (f 42) would have returned.
> Thence-forward, any call site in the guile code that had (f 42) in it would
> get replaced by the symbol (or the unboxed value)...
> 
> At the moment, I don't know how to tell apart 42, the literal number, from
> x, a symbol that references a number. (Somehow, I can't make symbol? work
> ...)  I also don't know how to "edit" the call site (the cell, the box?)
> that has (f 42) as the call-target, and replace it by a constant (or a
> boxed constant).
> 
> But my naive thinking fails:
> (define x 0)
> (symbol? x)  => #f
> (variable? x) => #f
> 
> So I guess that x is not a symbol, from the guile point of view!?  This is
> .. confusing. What is x, then, if not a symbol?

The issue here is that the expression "(symbol? x)" first evaluates the
expressions "symbol?" and "x" and then uses their values to go on.  So
it ends up being:

    (<value-of-symbol?> <value-of-x>)

So by the time the procedure behind "symbol?" is called, it neither
knows that it was called "symbol?" nor does it know that the value it
received, i.e. the integer 0, came from a variable called "x".

What you want to do is delve down to the macro layer so to say, by using
"define-syntax" and ideally "syntax-case".

Here's a demonstration:

  (define-syntax symbol-syntax?
    (lambda (stx)
      (syntax-case stx ()
        ((_ x)
         (if (symbol? (syntax->datum #'x))
             #'(display "yep\n")
             #'(display "nope\n"))))))

  scheme> (symbol-syntax? blah)
  yep
  scheme> (symbol-syntax? (blah))
  nope

What happens here is the following.  Going through it part by part.

  (define-syntax symbol-syntax?
    (lambda (stx)
      ...

You register a procedure (lambda (stx) ...) as a macro which you bind to
"symbol-syntax?".

Now the expression "(symbol-syntax? blah)" will not be evaluated in the
regular fashion, because Guile sees that "symbol-syntax?" is registered
as a macro.

Instead of trying to get the values of "symbol-syntax?" and "blah" like
it happened with "(symbol? x)", this time Guile calls the procedure
registered with "symbol-syntax?" immediately, and passes it a "syntax
object" that represents the whole expression "(symbol-syntax? blah)".
(Not just the "blah" argument but the whole expression, don't ask why.)

  (syntax-case stx ()
    ((_ x)
     ...

Within the procedure, we use "syntax-case" to do pattern-matching on the
expression "(symbol-syntax? blah)" that is stored in the variable "stx".

We know that the first thing in the expression is "symbol-syntax?"
because otherwise we wouldn't have been invoked in the first place, so
we want to ignore that, hence we use the pattern "(_ x)" which ignores
the first thing (via the underscore) and binds the second thing to "x".
That second thing is "blah" so "x" contains that now.

  (if (symbol? (syntax->datum #'x))
      ...

Now "x" contains the element "blah" but to your probable surprise, it's
not a symbol but rather a syntax object.  Thankfully we can just call
"syntax->datum" to get the corresponding data value, which is going to
be the symbol "blah".

You're probably wondering why we wrote #'x instead of just x in our call
to "syntax->datum" and to be honest I'm not 100% clear on the reason to
this day, but it's how syntax-case expects you to use pattern variables.
 You're never allowed to reference them "bare".

    #'(display "yep\n")

Finally, we use #' to create a whole new syntax object, containing the
expression (display "yep\n").  That syntax object is the value returned
from our procedure, so Guile puts it where "(symbol-syntax? blah)" was.

When you compile the code, all that actually ends up in the program is
"(display ...)" with no trace of the original "(symbol-syntax? ...)"
expression being left.  That's what macros do; they replace code at
compile time and leave no trace of themselves.

In the second call, "(symbol-syntax? (blah))", the pattern variable "x"
ends up containing a syntax object representing "(blah)", and calling
"syntax->datum" on that yields a list and not a symbol, hence we end up
returning '(display "nope\n")' from the macro.

----------

All that might be daunting at first but after a while it becomes
natural, and the power it gives the programmer is really like nothing
else. :-)

What you intend to do, which is traverse through a whole lambda body and
find instances of "(f 42)" to replace them, might be a bit tricky.
Let's say you've written a macro "my-define" which does that, then
consider the following definition:

  (my-define (foo x)
    (let ((f 42))
      (+ f x)))

Now you probably don't want to turn that "(f 42)" into anything else,
because it's not really a call to your "f".

I've never written a macro yet which does something like that, so I'm
not sure I can help, but I'm happy to respond to questions about the
general workings of the macro system.


- Taylan



reply via email to

[Prev in Thread] Current Thread [Next in Thread]