design issue with define-local-member-name or we need to understand Quine or referential transparency is BS but referentially transparent positions matter

40 views
Skip to first unread message

Matthias Felleisen

unread,
Mar 25, 2016, 10:13:20 PM3/25/16
to Racket-Dev List

Consider this program (a poem of a real problem I encountered):

#lang racket

(module server racket
(provide c%)

(define c%
(class object%
(super-new)
(define/public (m) 0))))

(module client racket
(require (submod ".." server))

(define-local-member-name m)

(define d%
(class object%
(super-new)
(define/public (m o)
(displayln (interface->method-names (object-interface o)))
(send o m))))

(module+ test
(require (submod ".."))
(send (new d%) m (new c%))))

(require (submod 'client test))

If you run this, you will get

Welcome to DrRacket, version 6.4.0.14--2016-03-19(-/f) [3m].
Language: racket, with debugging.
(o has methods: (m))
. . send: no such method
method name: m
class name: c%

So right after confirming that ‘m' is a method of o, our dear friend Racket tells us that m is not a a method of o.

I get it. The define-local-member-name ‘kills' the ‘m’ from ‘server’ as well as client outside of the scope, but that’s because Racket fails to acknowledge that some positions are referentially transparent and some are not when it replaces ‘m’ with the gensym-ed identifier that replaces it in scope.

I really would like this program to run and if not, I’d like to record it here why our implementation (as opposed to a specification) fails.

— Matthias

Matthew Flatt

unread,
Mar 26, 2016, 9:13:02 AM3/26/16
to Matthias Felleisen, Racket-Dev List
The error message should be improved to say something about `m` being a
local name, but the failure to call a method is certainly as intended.
That is, the use of `m` as a method name in a given scope is intended
to be statically the same in all forms that involve a method name,
including both `define/public` and `send`.

I don't get the "referentially transparent" suggestion. The `(send o
m)` call belongs to the `server` module, not whatever object is the
value of `o` dynamically. The method name `m` within `server` refers a
local name, unrelated to any method name that might be spelled the same
elsewhere. It's a question of the syntactic location of `m`, and the
syntactic location of `(send o m)` is definitely in the `server`
module.


I could imagine making the example work by redefining local names for
`send` as implying a dynamic search: first for the local `m`, then
whatever member name the local definition shadows, and so on. In the
example below, the final call would then produce 'b instead of 'a.

A search doesn't seem like a good idea to me. setting aside the
run-time cost, in places where I use a local member name, if the local
name is not present in the target object, then something has gone
wrong. My intent is never to call either the local name or a similarly
spelled non-local name, whichever is present.

But you didn't say anything about a search, and I'm not sure what you
had in mind. It's just my attempt to make sense out of what local names
could mean to make your example produce 0 instead of an error.

----------------------------------------

#lang racket

(module a racket
(provide a-mixin
a-m)

(define-local-member-name m)

(define (a-mixin %)
(class %
(super-new)
(define/public (m) 'a)))

(define (a-m o)
(send o m)))

;; just like `a`, but without `define-local-member-name`
(module b racket
(provide b-mixin
b-m)

(define (b-mixin %)
(class %
(super-new)
(define/public (m) 'b)))

(define (b-m o)
(send o m)))

(require (submod "." a)
(submod "." b))

(a-m (new (a-mixin object%))) ; 'a
(b-m (new (b-mixin object%))) ; 'b
(a-m (new (b-mixin (a-mixin object%)))) ; 'a
(a-m (new (a-mixin (b-mixin object%)))) ; 'a
(b-m (new (b-mixin (a-mixin object%)))) ; 'b
(b-m (new (a-mixin (b-mixin object%)))) ; 'b
(a-m (new (b-mixin object%))) ; error, but search would mean 'b

Robby Findler

unread,
Mar 26, 2016, 9:13:40 AM3/26/16
to Matthias Felleisen, Racket-Dev List
I think it makes sense (and might be implementable) for the error
message to say:

send: no such method
method name: m <boudn by define-member-name in x.rkt:33>

or suchlike.

Robby
> --
> You received this message because you are subscribed to the Google Groups "Racket Developers" group.
> To unsubscribe from this group and stop receiving emails from it, send an email to racket-dev+...@googlegroups.com.
> To post to this group, send email to racke...@googlegroups.com.
> To view this discussion on the web visit https://groups.google.com/d/msgid/racket-dev/F72F48F3-1989-42B9-8369-E8C96362C1DD%40ccs.neu.edu.
> For more options, visit https://groups.google.com/d/optout.

Matthias Felleisen

unread,
Mar 26, 2016, 10:03:03 AM3/26/16
to Matthew Flatt, Racket-Dev List

> On Mar 26, 2016, at 9:12 AM, Matthew Flatt <mfl...@cs.utah.edu> wrote:
>
> At Fri, 25 Mar 2016 22:13:16 -0400, Matthias Felleisen wrote:
>>
>> Consider this program (a poem of a real problem I encountered):
>>
>> #lang racket
>>
>> (module server racket
>> (provide c%)
>>
>> (define c%
>> (class object%
>> (super-new)
>> (define/public (m) 0))))
>>
>> (module client racket
>> (require (submod ".." server))
>>
>> (define-local-member-name m)
>>
>> (define d%
>> (class object%
>> (super-new)
>> (define/public (m o)
>> (displayln (interface->method-names (object-interface o)))
>> (send o m))))
>>
>> (module+ test
>> (require (submod ".."))
>> (send (new d%) m (new c%))))
>>
>> (require (submod 'client test))
>
> The error message should be improved to say something about `m` being a
> local name, but the failure to call a method is certainly as intended.
> That is, the use of `m` as a method name in a given scope is intended
> to be statically the same in all forms that involve a method name,
> including both `define/public` and `send`.


I think that this is the best workable proposal, especially if
inside of DrRacket, the ‘m’ in (send o m) were highlighted.
It would have saved me a few minutes. (Perhaps I should
make my modules even smaller than 900 lines.


> I don't get the "referentially transparent" suggestion. The `(send o
> m)` call belongs to the `server` module, not whatever object is the
> value of `o` dynamically.


If you think of names as things that stand for ‘real' values
and that ‘standing for’ is a substitution, you must acknowledge
Quine’s observation that some positions in a language (technically
sentences in a language) are referentially transparent — you can
replace equals by equals there — and others are opaque — don’t
perform substitution there.

[Our poor simple-minded cousins in the FP community jumped
from there to ‘referentially transparency’ (as a good thing) and
never recovered.]

My mind considers the m position in (send o m a …) a referentially
opaque position. Hence define-local-member-name, which substitutes
names through a scope, should not replace this m — even if m is
in its list of names. Similarly, in (printf “(send o m 1)”) define-local-member-name
should not peek inside the quotes. [This example is directly derived from
Quine’s treatise on language and everyone tends to agree. Other
positions in a language are subject to conversations among designers.]



> I could imagine making the example work by redefining local names for
> `send` as implying a dynamic search: first for the local `m`, then
> whatever member name the local definition shadows, and so on. In the
> example below, the final call would then produce 'b instead of 'a.
>
> A search doesn't seem like a good idea to me. ...
> But you didn't say anything about a search, and I'm not sure what you
> had in mind. It's just my attempt to make sense out of what local names
> could mean to make your example produce 0 instead of an error.


After sending my original message, I thought thru solutions and
search came to mind as the only obvious one in a dynamically typed
language. I am opposed to search on grounds of efficiency; we need
to keep ‘send’ as a ‘constant’ time check for the method. Programmers
expect that.

In languages that do not treat ‘send’ as a macro and in statically typed
languages (even unsoundly typed ones such as C++), I think it’s perfectly
okay if a ‘friend’ method m in C refers to a public method ‘m’ in class D.
That’s what I had imagined and that’s why I sent the message (though
I am hoping for an error message).

More generally, though, I think this problem might be a symptom of
a hole in our understanding of scope and how macros relate to scope
(which is why I bothered to write a long subject/message in the first place).
Perhaps a macro ought to be able to specify that a certain position in
the surface syntax belongs to ‘it’ and not the surface scope. If we could
find other instances of this problem, it would definitely be worth our
while to investigate. As is, I will leave it at the improved error message.


— Matthias










Ryan Culpepper

unread,
Mar 26, 2016, 12:23:11 PM3/26/16
to Matthias Felleisen, Matthew Flatt, Racket-Dev List
On 03/26/2016 10:03 AM, Matthias Felleisen wrote:
>
>> On Mar 26, 2016, at 9:12 AM, Matthew Flatt <mfl...@cs.utah.edu> wrote:
>>
>> At Fri, 25 Mar 2016 22:13:16 -0400, Matthias Felleisen wrote:
> [...]
>> I don't get the "referentially transparent" suggestion. The `(send o
>> m)` call belongs to the `server` module, not whatever object is the
>> value of `o` dynamically.
>
> If you think of names as things that stand for ‘real' values
> and that ‘standing for’ is a substitution, you must acknowledge
> Quine’s observation that some positions in a language (technically
> sentences in a language) are referentially transparent — you can
> replace equals by equals there — and others are opaque — don’t
> perform substitution there.
>
> [Our poor simple-minded cousins in the FP community jumped
> from there to ‘referentially transparency’ (as a good thing) and
> never recovered.]
>
> My mind considers the m position in (send o m a …) a referentially
> opaque position. Hence define-local-member-name, which substitutes
> names through a scope, should not replace this m — even if m is
> in its list of names. Similarly, in (printf “(send o m 1)”) define-local-member-name
> should not peek inside the quotes. [This example is directly derived from
> Quine’s treatise on language and everyone tends to agree. Other
> positions in a language are subject to conversations among designers.]

You should think of (send o [] a ...) as a referentially transparent
context (but a member-key context, not an expression context). The only
bug here is the printing of the variable name 'm' in the error rather
than what it refers to. (That's what Robby's suggestion would fix.)

> [...]
> In languages that do not treat ‘send’ as a macro and in statically typed
> languages (even unsoundly typed ones such as C++), I think it’s perfectly
> okay if a ‘friend’ method m in C refers to a public method ‘m’ in class D.
> That’s what I had imagined and that’s why I sent the message (though
> I am hoping for an error message).

"friend" is a concept that belongs to an access-control-based object
system, but Racket's object system is capability-based.

In particular, you could think of a hypothetical `(friend m)` form as
attaching some new access-control information to `m` (in the scope of
the friend declaration), but leaving the identity of `m` as a member key
alone. In contrast, `(define-local-member-name m)` substitutes
member-key references to `m` with a fresh unrelated key.

Ryan

Matthias Felleisen

unread,
Mar 26, 2016, 1:01:59 PM3/26/16
to Ryan Culpepper, Matthew Flatt, Racket-Dev List
On Mar 26, 2016, at 12:22 PM, Ryan Culpepper <ry...@ccs.neu.edu> wrote:

My mind considers the m position in (send o m a …) a referentially
opaque position. Hence define-local-member-name, which substitutes
names through a scope, should not replace this m — even if m is
in its list of names. Similarly, in (printf “(send o m 1)”) define-local-member-name
should not peek inside the quotes. [This example is directly derived from
Quine’s treatise on language and everyone tends to agree. Other
positions in a language are subject to conversations among designers.]

You should think of (send o [] a ...) as a referentially transparent context (but a member-key context, not an expression context). The only bug here is the printing of the variable name 'm' in the error rather than what it refers to. (That's what Robby's suggestion would fix.)


I agree with you here. I was wrong in calling this position referentially opaque. It is opaque from the perspective of local scope, but transparent with respect to the scope of ‘o’. Call it referentially translucent.  It’s the difference between lexical scope and static scope. 



[...]
In languages that do not treat ‘send’ as a macro and in statically typed
languages (even unsoundly typed ones such as C++), I think it’s perfectly
okay if a ‘friend’ method m in C refers to a public method ‘m’ in class D.
That’s what I had imagined and that’s why I sent the message (though
I am hoping for an error message).

"friend" is a concept that belongs to an access-control-based object system, but Racket's object system is capability-based.

In particular, you could think of a hypothetical `(friend m)` form as attaching some new access-control information to `m` (in the scope of the friend declaration), but leaving the identity of `m` as a member key alone. In contrast, `(define-local-member-name m)` substitutes member-key references to `m` with a fresh unrelated key.


The ‘friend’ words are mostly an analogy, not a true concept relevant to this discussion. Note though that the word has been used on this mailing list and ‘user’ in this context. 

— Matthias


Reply all
Reply to author
Forward
0 new messages