Earlier quoted context omitted.
Well, certainly no one seems to understand how e.g. syntax-case works. But my impression is that macro hygiene in itself is a solution looking for a problem. The key advantage e.g. racket's macro system has over clojure or common lisp is not hygiene but being sufficiently well structured and rich to allow proper tooling. Good error messages with accurate locations >> macro hygiene.
Macro hygiene is a solution to the problem of functions being in the same namespace as variables, together with standard, oft-needed library functions having short names that are easily chosen as variable names.
Exotic Programming Ideas, Part 3: Effect Systems
51–60 of 95 posts
Re: Exotic Programming Ideas, Part 3: Effect Systems
#52I've had this idea of "dynamic returns" (akin to dynamic scope) in my head for a while. Reading this, it feels like a dynamically typed companion to effect systems. The idea of a dynamic return is just to give a formal way to accumulate things during a set of function calls, without having every function to be aware of what might be happening. In Python context managers are often used for this (e.g., contextlib.redir…
The dynamic extent of (an evaluation of) an expression is the interval of the program’s execution starting when evaluation of the expression begins and ending when control flows out of the expression (ie it returns a value or does some kind of nonlocal transfer of control)
Something is lexically scoped if it may only be referred to by code which is syntactically inside the expression that creates the scope (eg in lisp, a let ordinarily creates a lexical scope but an anonymous function in the body of the let may continue to refer to the lexically scoped binding outside the dynamic extent of the binding; in JavaScript a var binding is in the lexical scope of body of the function in which it is bound)
Something is dynamically scoped if it is available for the dynamic extent of whatever creates the scope.
In Common Lisp most variables are lexically scoped inside the body of the let that defines them. Global variables (or other variables declared special) are dynamically scoped. One can write code like this:
(defvar x 0) ; x is global so dynamically scoped
(defun f (g) (let ((x 1)) (funcall g))
(let ((x 2))
(f (lambda () (print x))))
; => 1
(print x)
; => 0
If x were not defined as a global the output would be 2. If the language had some kind of block scope which was neither dynamic nor lexical, the output would be 0.This dynamic scope is often used for the kind of “dynamic returns” you describe. There are global variables called e.g. standard-output [written with an asterisk on either side but hn just makes it italic] which one may (dynamically) bind to another stream to capture the output.
Apart from the ordinary control flow out of expressions where they evaluate to something, there are three non local transfer of control constructs:
- catch/throw are a dynamically scoped way of returning values. They work similarly to exceptions in Java or JavaScript except that instead of catch dispatching on the type of the object which is thrown, it dispatches on the identity of a “catch tag” which is associated with whatever value is thrown, and there is no catch-all construct (so it is relatively hard to interfere with someone else’s catch/throw. This is the Common Lisp construct I would refer to as “dynamic return.” These aren’t used very commonly as the below operators tend to be preferred.
- block/return-from is lexically scoped but return-from is only valid within the dynamic extent of the corresponding block. Blocks are named by symbols.
- tagbody/go is much like block/return-from except it is a lexically scoped goto rather than a return. Either could be implemented (potentially less efficiently) in terms of the other.
There is another operator, unwind-protect, which works like a try–finally block in JavaScript to cause some code to run whenever the dynamic extent of an expression ends.
Another example of these scoping concepts is in the condition system which handles errors and other conditions (like warnings or “signals”.) Typically this is implemented using a dynamically scoped variable holding the list of handlers which are currently in scope, and another for the restarts which are just functions. When a condition is signalled, the list of handlers is searched for a suitable handler which is a function that is called. This function has access to the lexical scope from where the handler was defined but runs in the dynamic scope from where the condition was signalled. It may choose to invoke one of the dynamically scoped restarts (proposed solutions to the condition, eg retry or abort) which is just a function that will typically transfer control to some place near to where it was defined. This is different from the traditional exception systems where by the time you catch an exception you’ve already unwound a lot of the stack (so it’s hard to resume from an earlier stage now.)
Re: Exotic Programming Ideas, Part 3: Effect Systems
#53Re: Exotic Programming Ideas, Part 3: Effect Systems
#54Earlier quoted context omitted.
Well, certainly no one seems to understand how e.g. syntax-case works. But my impression is that macro hygiene in itself is a solution looking for a problem. The key advantage e.g. racket's macro system has over clojure or common lisp is not hygiene but being sufficiently well structured and rich to allow proper tooling. Good error messages with accurate locations >> macro hygiene.
> macro hygiene in itself is a solution looking for a problem No. Unless you're not familiar with Lisp-1 vs Lisp-2. In Scheme, you would have to GENSYM every variable in addition to every function you call within a macro. Whereas in Common Lisp you just need to GENSYM the variables. That's the real reason Scheme doesn't use DEFMACRO. I'm not personally a fan of any hygienic macro system because learning a new languag…
Just because "classical" defmacro is mildly awkward to use in Common Lisp and much more so in scheme does not mean that the only viable alternatives are R*RS style hygienic macros. Look at how e.g. clojure does it, it has a simple and perfectly adequate solution (a concise gensym notation, basically). As far as hygiene is concerned. Not so much in terms of generating good error messages.
Re: Exotic Programming Ideas, Part 3: Effect Systems
#55Earlier quoted context omitted.
> Oleg can write unhygienic syntax-rules macros. Not only can he do it, he even wrote a paper about it: https://citeseerx.ist.psu.edu/viewdoc/download?doi=10.1.1.36...
IMO implementing hygienic macros properly is one of the hardest tasks in programming languages, especially since variable capture can cause subtle bugs.
Then it is disappointing when you realize you can't show it at work or people get ideas...
Re: Exotic Programming Ideas, Part 3: Effect Systems
#56Re: Exotic Programming Ideas, Part 3: Effect Systems
#57Earlier quoted context omitted.
I mean... if you're fixing a broken API, you're going to have to bite the bullet either way -- papering over it isn't going to fix anything... it just hides the problems. With a type system which understands effects, at least the compiler can give you very accurate help in fixing call sites.
It does help by telling you what's wrong. But in a way, increased precision makes the problem worse. Suppose you have have a language with two categories of functions, those that can fail (returning an error) and those that can't. It's nice that within the "functions that can fail" category, you don't have to worry about what kind of error it might be. Error propagation can happen in a generic way. Adding a new kind…
Also, breaking API changes happen in every language, on every codebase, ask the time. At least with static types and a common linter I can refactor and be a hundred percent sure it works since it compiles, whereas in Python for example I can't be sure unless I add tests and stuff, even then...
Re: Exotic Programming Ideas, Part 3: Effect Systems
#58I've had this idea of "dynamic returns" (akin to dynamic scope) in my head for a while. Reading this, it feels like a dynamically typed companion to effect systems. The idea of a dynamic return is just to give a formal way to accumulate things during a set of function calls, without having every function to be aware of what might be happening. In Python context managers are often used for this (e.g., contextlib.redir…
> E.g., if your effect is writing to stdout, you could rewrite all those changes Could you? Once you write something into stdout, as far as your program knows it could already be sent across the world and turned into a set of bank transactions, or missiles fired.
Re: Exotic Programming Ideas, Part 3: Effect Systems
#59I expect that, as with any other type system extension, the more granular your effects are, the more likely you are to run into a “what color is my function” problem. If you have a public API that declares certain effects, you’re stuck with those unless you break backward compatibility. In a practical system, when writing a library and especially an abstract interface, you’d want to be careful what you promise and de…
Re: Exotic Programming Ideas, Part 3: Effect Systems
#60Arrow Fx implement this idea for Kotlin -> https://arrow-kt.io/docs/fx/
Scala has a library for the difficult part, but I don't think it was very successful: https://github.com/atnos-org/eff