What Template Haskell gets wrong and Racket gets right
blog.ezyang.com
blog.ezyang.com
R5RS: [0]
R6RS: [1]
R7RS: [2] (Page 22)
And many Schemes provide non-standard extensions; for instance, Chicken supports non-hygienic macros[3].
FWIW, I prefer to think of the two stages as "syntax expansion" and "evaluation", and not "compile time" and "run time". Compilation and running are loaded terms that don't appropriately capture the totality of situations where this behaviour applies, in Scheme.
0: http://schemers.org/Documents/Standards/R5RS/HTML/r5rs-Z-H-8...
1: http://www.r6rs.org/final/html/r6rs/r6rs-Z-H-14.html#node_se...
2: http://trac.sacrideo.us/wg/raw-attachment/wiki/WikiStart/r7r...
0: http://synthcode.com/scheme/chibi/lib/chibi/scribble.html
Does Racket have a way to address the need to, say, create a dispatching table during syntax expansion and read from that table at runtime? I don't know Racket well, but I've been able to do similar things easily in Guile, Common Lisp, and Clojure without an issue. Googling this problem leads me to believe that it can't be done, which is a pretty severe handicap to have if you're a Lisp.
Also adding that what I was doing in Guile, Common Lisp, and Clojure was sloppy. After searching for the case macro, saying 'aha!', and applying what I learned into creating an equivalent Racket macro, I realize that it was a portable solution to Guile, and the same idea applies for non-hygienic macro systems.
(If the example is not too long, perhaps you can paste it here.)
R7RS small maintains this same mentality. R7RS big is basically R7RS small with a large standard library.
1. kitchen sink
2. extract core
3. redefine kitchen sink as `small` + extrasThe problem people have been trying to work through is that many people like a small, clean, minimalist core Scheme, but many other people (well, some are the same people, myself included) don't think the current standard is enough to produce useful interoperability. Especially implementors like being able to write small self-hosted experimental or embedded Schemes without a large standard. But many people writing Scheme code are not all that happy about the poor interoperability story that ends up resulting, because there is not quite enough standardized to write libraries that will work on all the major schemes, which is why there's no Scheme equivalent of CL's Quicklisp repository of cross-implementation libraries (the SFRI system is the only real attempt to make this sort-of happen, and is not really good enough).
And yet, R7RS-large _still_ will not have a standardized FFI.
0: http://en.cppreference.com/w/cpp/language/language_linkage
1: https://www.haskell.org/onlinereport/haskell2010/haskellch8....
2: https://docs.oracle.com/javase/8/docs/technotes/guides/jni/
3: https://common-lisp.net/project/cffi/
4: https://en.wikipedia.org/wiki/Foreign_function_interface#Exa...
This solution is hugely superior to having none at all; as it means that bindings to foreign libraries may trivially be ported across many Schemes. Choosing the right Scheme will be less about what libraries are available, and more about which provides the best execution environment for your needs.
In this case, doing the right thing happens to be doing nothing more than warning the user, as is often the case. Attempting to design a system that safely captures foreign state with continuations is almost certainly an over-engineered solution.
For R7RS-large, are there plans for a module standard?
[1]: Hudak P., Hughes J., Peyton Jones, S., Wadler P. (2007). A History of Haskell: Being Lazy With Class.
[0]: https://stackoverflow.com/questions/10857030/whats-so-bad-ab...
The point could have been worded better I think.
The larger the macro, the more cryptic the errors are as well, since you could conceivably be generating entire functions whose output and input types are inferred based on very distant macro input and context, and such a type error is going to be very confusing to your users. C++ has this problem: simple errors can dump a dozen screens of template garbage on you.
Depends what you mean by "metaprogramming". Most people would consider type class "abuse" metaprogramming, but it's definitely not something inspired by C++.
In my own words, code that can read and write code (whether that's at compile time or run time).
[1] https://mail.haskell.org/pipermail/haskell/2006-August/01835...
Macro By Example: ftp://www.cs.indiana.edu/pub/techreports/TR206.pdf
Dybvig's classic paper: https://www.cs.indiana.edu/%7Edyb/pubs/tr356.pdf
I'd also like to point out that, contrary to popular belief (for some bewildering reason), syntax-case is capable of expressing non-hygienic macros via the `datum->syntax` function, which takes an identifier and a symbol, and returns a new identifier with the same name as the symbol and the same scope as the provided identifier. The R6RS even gives an example of writing a completely unhygienic, Common Lisp-like macro transformer via syntax-case (section 12.6 in the R6RS Libraries document [0]).
syntax-case has a bad rap, but it's really not a difficult system to learn, and it's incredibly powerful. In particular, I think its "hygiene by default, with per-identifier hygiene-breaking when and where you want it" is the best possible policy for a macro system (as far as I'm aware, the only other macro system with the same policy, though imo with a much more intuitive interface, is Chicken's ir-macro-transformer for implicit-renaming macros [1]). The combination of syntactic pattern-matching and procedural expansion is extremely convenient, too: you never need to have a massive `let` form that binds the car, cadr, caddr, cadddr, ... of the input form because the patterns let you destructure and switch (as in case analysis, thus syntax-case) on the input form much as in ML-style pattern matching. Of course, you could argue that the pattern matching ought to be disjoint from the notion of a macro transformer, and I'm inclined to agree, but my understanding is that implementing it the way it is makes it easier to keep track of which variables are "pattern variables" which helps to provide hygiene (someone please correct me if I'm wrong).
Whenever a new language comes out with an unhygienic macro system (as in, unhygienic by default), I immediately headdesk. Hygiene is a very desirable property of a macro system, and there's really no reason not to provide it. A simple hygienic macro system (like syntactic closures, for example) is only slightly more complicated to implement than an unhygienic one, and you're doing your target audience a huge favor. No, gensym is not enough.
I'm also bewildered by arguments that define-macro (Common Lisp-style macros) is easier than syntax-rules. The builtin pattern-matching offered by syntax-rules makes writing macros so much easier. Yes, template macros are less powerful than procedural macros, but in my experience they cover upwards of 90% of macros you might want to write, and they do away with tons of boilerplate code. For the other 10%, most Scheme systems do offer procedural macros in addition. I was a little disappointed that R7RS left syntax-case out because it was nice to finally have a standard facility for defining low-level and procedural macros. Perhaps there's hope yet for R7RS-large.
[0]: http://www.r6rs.org/final/html/r6rs-lib/r6rs-lib-Z-H-13.html...
[1]: http://wiki.call-cc.org/man/4/Macros#ir-macro-transformer
Functions defined in a file where a macro is defined and used are also not seen by the file compiler. The use of (EVAL-WHEN (:COMPILE-TOPLEVEL) ...) instructs the file compiler to execute the enclosed forms at compile-time. Thus one can tell the compiler to define a function in the compile time environment or to load some code at compile time via LOAD or REQUIRE.
Common Lisp is a very explicit language, and that explicitness allows macros to easily coexist with other code, because nothing is hidden by syntax or "black magic". (By default. Because you can morph the language to do nearly anything you'd want to.)
I think of "deriving" in Haskell as a form of automated theorem proving, and I think it would be beneficial to have better theorem prover integrated in the language (I think it is in languages like Coq).
The reason why I think theorem prover would be more appropriate than macros is because with macros you, as a programmer, have to do the proof explicitly, and you're responsible for it being type correct. Whereas with a real theorem prover behind the scenes, this work is not needed.
In general, I think it's still quite unclear how to well integrate macro system of Lisp power with type checked language.
So risking "moving the goalpost", I am not really satisfied with your answer. Are there some uses cases for macros that do not involve theorem proving (i.e. having compiler build some function automatically based on type signature alone)?
Should I also mention that I feel that this is endemic of Racket itself, which tries to be the be-all-end-all by taking everything from everyone, but winds up being an awkward mess, as it tries to integrate DBC with FP and a (fairly weak, iirc) OO system, not to mention custodians, and a ton of other stuff that should have been spun off into modules and libraries, but is cluttering up core and makes me long for chicken, and want to hurl whenever I look at it?
Don't get me wrong, a big stdlib is always handy, but for god's sake, pick a SMALL set of core concepts and stick with them.
Now if you'll excuse me, I'll go get some kevlar. Knowing you lot, I may need it after this.
Custodians are in the core, but it's not possible to move them to a library -- they necessarily have to integrate with things like the garbage collector. You couldn't have the things they provide, like the ability to limit part of your program to a specified amount of memory, without that.
The reasonable default is writing programs with structures and functions. Most people use OO primarily for the GUI (where OO works quite nicely). This is the same paradigm that you'd find in ML or Rust or Go in many cases.
The docs could have fooled me. You try to learn anything about Racket, and pretty soon, you're 10 levels deep into nonsense about contracts and other junk that you shouldn't need to see just to find the semantics of cons, or define. Which is to say, the docs are bad. But that's excusable.
What I do NOT find exusable, and what started this conversation, is that syntax-case is overcomplicated compared to, say, er and ir macros, or syntactic closures, all of which were already available. So use a more complex system, when it's been shown a simpler one will do?
I don't see 10 levels of nonsense there. But clearly you're not happy about larger aspects of the system that it's probably not worth debating here. But let's just say that (a) syntax-case is a pattern matching library, not a macro system, and (b) Racket's macro system has demonstrated its usefulness by building those libraries that you claimed were built in, such as OO systems and contracts.