I don't think this has come up before. An f-expr based Lisp would automatically have this property (at much performance cost). But we haven't discussed lexical macros as a separate construct.
I feel like something kinda related has come up a couple of times before: allowing local variables to override macros. I think there was even a one-line patch to ac.scm at some point. But it must have had some issue since it's not in Anarki :)
It should be possible to get the continuation from the point of failure and the dynamic variables from the failing thread (basically: the stack), the same information from any other running threads, and the set of global variables (this at least can be gotten with (namespace-mapped-symbols)), and trace the graph of reachable objects from there, and serialize it all to a file. I don't know if Racket provides the ability to do all that, though; for one thing, I don't know if there's a way to access the variables saved in a closure (from outside the closure).[1] (Maybe using unsafe operations could do that.) Since tracing the graph of objects is exactly what a GC does, and a proper moving GC has to be able to learn the type of every object and where all the pointers are, it must have that functionality, whether or not it's exposed. (I think it should be exposed, of course.)
Barring that, it's possible that the gdbdump rocketnia points at is the easiest way to do it in Racket.
Also, I guess if you're using the FFI at all (which, say, any GUI program would do), then you do need the full core dump if you want to get the state of the C libraries you're using.
In Lathe and in the first version of Penknife (written in Arc), I was calling this kind of feature "failcall." A function could be called with `failcall` to handle its failures, or it could be called normally, in which case its failures would be promoted to errors automatically.
Your example of using Racket parameters leads to a slight difference in behavior from what I would want. Suppose the code in the body contains a call to some function that in turn makes a normal call to another function which fails. With the Racket parameter technique you talk about, the parameter binding would still be in scope at that point, so the failure would be caught, even though I think the author of that normal function call would have expected its unhandled failures to be promoted to bugs.
I remember thinking Racket parameters would be useful, but the technique I ended up with didn't use them at all. There's a full-featured implementation in the Lathe arc/ folder's failcall.arc[1], but here's a short proof of concept for Anarki:
; In this example, a "failfn" is a tagged single-argument function
; that returns (list t <success-val>) or (list nil <failure-val>).
(mac failfn (x . body)
`(annotate 'failfn (fn (,x) ,@body)))
; To call a function in a way which handles failures, we pass in an
; argument and an `on-fail` handler like so. This can be used with
; normal functions too, which just never fail.
(def failcall (f x on-fail)
(if (isa f 'failfn)
(let (succeeded val) rep.f.x
(if succeeded
val
(on-fail val)))
f.x))
; When a failfn is called normally, it behaves as though it was
; failcalled with a handler that always produces an error.
(defcall failfn (f x)
(failcall f x
(fn (failure-val)
(err:+ "Failed with " (tostring:write failure-val)))))
; We define an example failfn. We can't use `def` for this since it
; defines a normal function.
(= failure-prone-sqrt
(failfn x
(if (< x 0)
(list nil "Tried to take the square root of a negative number")
(list t (sqrt x)))))
arc> (failure-prone-sqrt 4)
2
arc> (failure-prone-sqrt -4)
Failed with "Tried to take the square root of a negative number"
context...:
/path/to/anarki/ac.rkt:1327:4
arc> (failcall failure-prone-sqrt 4 idfn)
2
arc> (failcall failure-prone-sqrt -4 idfn)
"Tried to take the square root of a negative number"
arc> (failcall sqrt 4 idfn)
2
arc> (failcall sqrt -4 idfn)
0+2i
The REPL transcript shows me calling a failfn using a normal call, calling a failfn using a failcall, and calling a normal function using a failcall. The only case that causes an actual error is when the failfn fails and there was no handler to catch it.
Obviously, a more full-featured approach would allow failcalls of arity other than one. And this `failcall` syntax doesn't have the convenient kind of pattern-matching syntax your `onfail` macro does, but that kind of thing could be built as a layer over the top of this example; I'm just keeping the example small.
Racket is just as capable of this technique as Anarki is. Instead of an `annotate` tagged value, the Racket version would use a struct, and instead of `defcall`, it would use the `prop:procedure` structure type property.
As far as making core dumps goes, I've never tried this, but it looks like `gdbdump` might be able to do it for Racket programs on Linux.[2] There's also a Racket built-in called `dump-memory-stats`,[3] which at least in Racket 7.0 appears to give a summary of how many objects of certain kinds are in memory.
Didn't consider a double `%then` or `%else`, thanks. Raises some interesting problems.
Generally the plan is to have a special character control whether it gets bound or inlined, probably `!`. So say `%test!` would get inlined like right now, while using `%test` would bind (and doing both would also be possible). But for a `%then` you'd generally only want to bind if there's 2+ so I could count usages instead.
Thanks for that example. I see now that you mostly only need to worry about multiple evaluation for the `%test` branch; `%then` and `%else` should be exclusive anyway, and I'm not concerned about the growth in macroexpanded code when duplicating a few s-expressions.
You could still have repeated use within a '%then' or '%else' block:
(aif (test)
(something)
(do %then %then))
But it should suffice to perform one evaluation in each branch. Cool! That seems simpler than some of the alternatives I'd been thinking about. I'd try to evaluate everything ahead of time and then realize that I shouldn't run `%else` if the `%test` returns a truthy value.
Author here. Glad to see some interest, sometimes I lurk here and now I feel bad not having submitted here myself.
Interestingly the "%else" would actually be cataphoric, as you refer to what comes next rather than before. So "co-referential macros" would be more fitting if you want to stick with the linguistics analogy. But that'd be too exotic of a term.
And yes as akkartik notes, it causes multiple evaluations right now, mostly just laziness and indecision on my part.
I'll probably be giving control over this. Here's a real example of code where you actually want current behavior:
A failure that is unexpected and unplanned for is a bug. Thus it's a bug if a file doesn't exist and my code doesn't handle that situation.
The boundary is what I want to happen in response to a bug vs. a failure. When I hit a bug, an actual bug, I want to capture the entire state and history of my program, to the fullest extent possible, so that I can find out why the bug occurred. I don't care if this a core dump is GBs in size or might be expensive to generate. If a bug occurs I want all possible information that might help me, everything that the language runtime can produce.
For failures, for expected failures, for failures I handle, I don't need to capture anything. I don't even need a stack trace. I don't need the language runtime to generate a stack trace every time I hit an expected, handled failure.
Existing languages don't allow me to do this. At the point where for example the "file not found" exception is being thrown there isn't enough information to tell whether that's a failure or a bug, so they have to be handled the same.
I'm not sure I grok the precise boundary you're drawing here.
It seems clear that (car 10) is always a bug, so I'm with you there. However, non-existent files may be bugs in some situations. Perhaps you're just proposing giving programmers two distinct labels to use with discretion? If so I shouldn't get hung up on precise examples.
> I just wanted to throw together a quick demo of the Arc tooling I've been working on.
To what end, might I ask? i.e. are you planning to contribute this to the arc community? Is this something the community can leverage with their own tooling?
I could have other questions, but if it's not something I can use or look at then they become somewhat pointless (the questions, that is).
"Note that as of right now the symbols are replaced by copying in the expression it references, not by binding to a common variable. Hence not suitable for using with expressions that cause side-effects or involve a lot of computation. That will be changed soon."
I presume, by reading this, these expressions will be evaluated each time they are triggered within the operation, so the answer is - yes they will. But, as the read-me also suggests, this is a "WORK IN PROGRESS" and the author has stated intentions to assign variables, which would solve the issue.
Staring at these examples again, another thought occurs to me: do these macros cause multiple evaluation, or are the equivalences above just loose? Can anybody tell? I can't tell just from skimming the implementation.
If they're doing multiple evaluations they're a lot less useful than Paul Graham's original anaphoric macros even if they seem superficially more powerful/expressive.
Racket doesn't have anaphoric macros as part of the core language (although there is a module for that), so that's a bit difficult getting used to when coming from Arc, but I find that pattern matching[0] can be used to the same effect:
(match 123
((and (? even?) it) (~a it " is even"))
(it (~a it " is odd")))
"In `acond`, `aif` and `awhen`, `%test` or `%t` gets replaced with the 'test' form. `%then` gets replaced by the 'then' form, and `%else` by the 'else' form."
"If nested you can access the previous level by doubling the first letter of the symbol. For example, '%ttest' would get you the previous [containing?] 'test' form, while '%eeelse' would get you the 'else' form 2 levels up. In the `aand` and `aor` macros you can reference arguments by using a symbol of form `<star><num>` where 'num' is the 1-index of the argument. Previous levels are accessed by doubling the `<star>` character. So the second 'test' form of an `aand` can by accessed with `<star>2` and the third argument of the previous [containing?] `aand` would be `<star><star>3`." [Working around the crappy pseudomarkdown here.]
Saw this on reddit and given that it's derived from Arc I thought I'd post it over here.
Certainly these are much more involved than my functions. My aif for example is:
(defmacro aif [expr & body]
`(let [~'it ~expr]
(if ~'it
(do ~(first body))
(do ~@(next body)))))
Actually, when I started in Clojure I was using these anaphoric operations a lot, but most of my code has moved away from them (for no particular reason, I just haven't needed them much I guess).