
At the “edge” of a program, “accepts whatever you pass it” can be useful — as a kind of input validation, especially from humans or from messy files or APIs. “Do what I mean”.

But I get uncomfortable when that permeates the whole program. I think it actually makes it harder to catch problems near where they originate.

So ideally I like to keep the DWIM part as a pretty thin outside edge/layer. And pretty quickly try to get very “brittle”, where contracts (or static types) are being super picky about what functions accept, and loudly complain about surprises.

But. That’s just my own guideline, for myself. It’s not always so simple or easy to structure things that way. ¯_(ツ)_/¯

Is there any way to write a macro that binds an identifier the first time its expanded, but on subsequent expansions (in the same scope) it doesn’t. That is, a macro, say (define-foo x body)
, that can expand like:

(let ()
(define-foo x body))
=> (let ()
(define foo some-expr)
(define x body))

BUt if we call it twice, foo
only gets bound once. So:
(let ()
(define-foo x body))
(define-foo y body)))
=> (let ()
(define foo some-expr)
(define x body)
(define y body))

?

@leif no idea, but very good question. I can think of tons of use cases for that.

You could always do it with some mutable state.

I wouldn’t trust an implementation that did though

I dunno, definition contexts are imperative, anyway, and to quote Sam paraphrasing Ryan, “I can’t think of a better way”. :)

You can use the value of syntax-local-context
to check if you’ve already added a definition in the current definition context or not.

maybe it could be done with an approach similar to racket/splicing
, where an intermediate step performs local expansion?
(with-definition-merger (lambda (first-id second-id) ...)
(define-foo x body)
(define-foo y body))

I really do think you can reliably put the value of (syntax-local-context)
in a phase 1, mutable, eq?
-based hash set and use that to check whether or not you’ve “seen” the current definition context or not.

Any situation in which that isn’t true means someone else did something wrong.

I don’t disagree with that, but I also think it’s a solution to a very narrow set of problems

I don’t think I really understand. It’s the recommended solution to the very general problem of asking “have I seen this definition context before?”

What I mean is that I don’t think asking that question is something macros should be doing, because it inserts temporal dependencies between them. I don’t want to have to debug that kind of code.

Expansion fundamentally has an arrow of time, and @leif’s request depends upon it, so I don’t think you can get away from that entirely. Though of course you could wrap it up behind some abstraction if you wanted.

@lexi.lambda Ya, you’re probably right. Although I may be doing something wrong, because I keep getting what it believes is the same internal definition context: (define-for-syntax ctxs
(mutable-set))
(define-syntax-parser define-foo
[(_ x:id body)
(set-add! ctxs (syntax-local-context))
(writeln ctxs)
#'(define x body)])
(let ()
(define-foo x 42)
(void))
(let()
(define-foo x 48)
(void))

Make sure you’re using mutable-seteq
, though that might not be what’s going wrong there.

Ah, okay, that helped.

Both macros could emit the duplicate definition within some outer macro that locally expands its body and deduplicates definitions. Then there’s no dependency on the order that individual body transformers are called.

Still, odd that internal-definition-context?
is returning #f
for (syntax-local-context)

Also, if you’re in an internal definition context, (syntax-local-context)
is a list. You probably want to get the first element out of the list and use that.

The value inside the list returned from (syntax-local-context)
can be anything. It’s only guaranteed to be different with respect to eq?
in different contexts.

Oh yes, it is a list,with a liberal-definition-context in it.

Doesn’t the docs say its one of several symbols or a list?

AH, okay

Yes, it is a list when you’re in an internal definition context.

You probably want to allow 'module
or a list, and nothing else.

Well, maybe also 'top-level
, if you want to support that.

THat would explain why I could never get internal-definition-context-binding-identifiers
to work.

You can put 'module
directly in your mutable set without worrying about it, since there can only be expansion in one module per visit to your module. So you’ll never end up with 'module
being returned for two different modules before your mutable set is emptied.

So, to summarize, you probably want something like this: (define-syntax-parser define-foo
[(_ x:id body)
#:do [(define ctx (syntax-local-context))]
#:fail-unless (or (memq ctx '(module top-level)) (pair? ctx)) "only allowed in a definition context"
#:do [(define ctx-v (if (pair? ctx) (car ctx) ctx))
(define seen? (set-member? ctxs ctx))
(set-add! ctxs ctx)]
#\| do something with seen? \|#])

Interesting. Is there any reason for the last #:do
other than style?

No.

(As opposed to just putting it in the body.)

Okay, so just style?

Yes.

Cool.

Also thx.

oh, one more thing, why did you get the first element of the list if its an internal definition context?

Since the list itself is eq? if the context is eq?

Oh…durp….the docs

". The identity of the list’s first element (i.e., its eq?ness) reflects the identity of the internal-definition context"

thanks. :slightly_smiling_face:

Yes, I’ll be honest, I am not certain exactly when (syntax-local-context)
would be a list of more than one element.

Honestly, you’re guess is as good as mine.

If anyone knows, its one of @mflatt, @ryanc, or @michael.ballantyne

The local-expand
documentation states: > If the internal-definition context is meant to be self-contained, the list for context-v
should contain only the generated value; if the internal-definition context is meant to splice into an immediately enclosing context, then when syntax-local-context
produces a list, cons
the generated value onto that list. However, I do not know when an internal definition context would splice into an enclosing one in a way that would still be a distinct definition context.

When begin
splices into an enclosing context, expansion of the forms in the begin
are still in the same context as the enclosing one.

@greg Thanks. I appreciate the voice of experience about this. Lately I have indeed been going wild with DWIM arguments, deep in internal function calls. Hopefully I won’t get burned! When I started with Racket, I kept getting burned by forgetting whether something was a list or a set (also in Clojure). If I changed which one a function accepted or returned, it was hard to track down what other code needed to be changed. How do people deal with that under dynamic typing?
Many little things are error-prone in that way, e.g. “pass the struct” vs. “pass the thing inside the struct”. So far, it’s been working out: the calling code is now simpler (e.g. no more set->list
or list->set
wrapping arguments to function calls) and more readable. But I’ll watch out. Do you know of something bad that results from functions that take whichever representation they’re given?

@ryanc I put in the closure that accepts the class name in the “deep” syntax class. It worked easily, and even let me simplify some code by making syntax classes no longer return so many attributes. Thanks again!

I think I saw this in the implementation of splicing-local the other day.

It’s an alternative implementation of a splicing definition context that uses local expand rather than trampolining in the context to be spliced into.

So, I mean, the usual caveat applies: It depends. :smile: Early on, as I explore something new, I define some functions — without contracts. I have “types” in my head, loosely. I try calling these functions from the REPL, supplying various examples, see if they behave as I expect. As things “settle down”, I might copy some of those REPL example calls inside check-equal?
forms in (module+ test ___)
submodule, as tests. If the function signatures seem “obvious”, I might stop there, for awhile. Or, I might also add contracts. But I might wait until things gel, and I feel confident about the decisions. (I think “premature ceremonialization” is bad for similar reasons as premature optimization.)

> Do you know of something bad that results from functions that take whichever representation they’re given? There’s some (Alan Perlis??) quote: “If you have a function with 10 arguments, you probably missed some.” I feel like a variation of that is, “If you have a function argument that can be any of 10 different things, you probably missed some.” I just feel like it’s hard enough to make code really correct under all edge cases. So I don’t want to add even more edge cases. Also if a function expects an integer, but the caller gives it a string — the caller is probably confused about something. The sooner the callee complains, and the caller can be fixed, the easier it is. (whereas if it blows up 5 or 10 function calls away from that original confused function, it’s going to be harder to fix. Especially if you don’t notice the bug until long after the bad thing was written.)

So again, some early version of a function, maybe I hedge my bets, don’t commit to firmly to what the acceptable values are. That’s part of exploring the problem space. But I like to tighten it up as I go along. ¯_(ツ)_/¯

Also, @mbutterick described how, over time, you might break a big bunch of code into smaller modules, maybe even publish them as their own packages. Certainly by that time, it’s nice to firm things up. “This function does this, here’s what it expects from you, here’s what you can expect from it, yada yada.”

</wall-of-text>

One problem with a type like DynamicAny
is that its contract would be flat, but for type soundness we need make-predicate
on it to fail. We cannot allow a typed function to produce true with the proposition #:+ DynamicAny
on it. The contract should only be used for either cast
, require/typed
, or provide
, and never for make-predicate
. Is there a way to specify that restriction in Typed Racket’s framework?

A predicate of type (-> Any Boolean : #:+ DynamicAny)
returning true would be unsound, but a predicate of type (-> DynamicAny Boolean : #:+ DynamicAny)
would be fine.