tomhrr / dale

Lisp-flavoured C
BSD 3-Clause "New" or "Revised" License
1.02k stars 48 forks source link

Suggestion: (attr once) for macros to enable safe macro side-effects #200

Closed ChengCat closed 5 years ago

ChengCat commented 5 years ago

In general, a macro may be evaluated more than once, for the following cases:

  1. speculative evaluating to determine argument types
  2. type-of
  3. eval-expression
  4. eval-macro-call

Therefore it's not safe in general for macros to have side effects. On the other hand, macro side-effects are useful, so I suggest that we introduce a new attribute (attr once), for those macros which do have side-effects to be safely written. This attribute will guarantee the macro is evaluated at most once, by prohibiting evaluation in all the above listed cases.

It's still possible for one appearance of macro in source code to be evaluated more than once:

(import cstdio)
(import macros)
(using-namespace std.macros
  (def m (macro intern (void)
    (printf "m is evaluated\n")
    (mnfv mc 1)))
  (def dup (macro intern (x)
    (qq do (uq x) (uq x)))))
(def main (fn extern-c int (void)
  (dup (m))
  0))
ChengCat commented 5 years ago

Another idea is (attr notype). notype macros won't be evaluated only to determine its return type, and the situation of multiple evaluation of notype macros is similar to that of traditional LIsp languages.

porky11 commented 5 years ago

ChangCat: I think you are right, that evaluating the expressions of macros is in most cases not a good idea, because in most cases, you would just want to use macros without ever directly evaluating the forms, like let does.

Scopes for example will have two different kinds of macros: One, that works on the S-Expressions directly, like traditional lisp, and one that works on a typed AST. Like there I would prefer two different kinds of macros, one just syntax and one with type information. Maybe the one with type information could even be implemented on top of the one without, by manually evaluating the yet untyped forms and calling typed version then.

(attr notype) seems like the best simple solution for now, and would also do the same as two different macro kinds.

ChengCat commented 5 years ago

@porky11 I know of Scopes, and feel that its approach with two different syntax tree is weird, i.e. one is the written sexp, and the other is the underlying semantics. In traditional Lisps and Dale, there is only one kind of syntax tree. Having two different kinds of syntax tree seems to be an unwanted complication. I have tried to reach its author along with some other concerns, but unfortunately haven't got any reply yet.

I think Dale's approach is fine. It's already not perfectly safe to do side effects in a macro in traditional Lisp languages, and Dale's type-of-related mechanisms just opens up more possibilities for a macro to be evaluated multiple times. This breaks an assumption we implicitly hold when writing Lisp programs, but it is mostly fine when the macro is without any side effects. I think providing a mechanism for safe macro side-effects would be nice. To be honest, I am not entirely satisfied with my suggestions, I hope someone can come up with something better.

ChengCat commented 5 years ago

Another idea is that, we forbid overloading the same symbol with both functions and macros, when we can't determine only by the number of arguments whether it's function or macro to dispatch. This way, operator-macros will still work, and an entire class of multiple evaluation of macros is eliminated.

This idea can be combined with (attr notype), to get a behaviour similar to that of traditional Lisp.

porky11 commented 5 years ago

That also was my idea, that (attr notype) would disallow defining macros with the same types.

ChengCat commented 5 years ago

Closing this issue, since we now have a better idea in #201 to enable safe macro side effects, without any attribute annotation.