janet-lang / janet

A dynamic language and bytecode vm
https://janet-lang.org
MIT License
3.51k stars 227 forks source link

Multiple top-level forms in defmacro? #126

Closed Ruin0x11 closed 5 years ago

Ruin0x11 commented 5 years ago

Is there a way of getting back more than one top-level form from defmacro? I think Clojure and Racket have this feature. I want to make a macro that can output this:

(defn my-fn [& rest]
  (string "foo"))
(put all-fns :my-fn my-fn)

However, it seems defmacro only outputs the last form if more than one is given, so you can't do this:

(defmacro my-defn [name & more]
  ~(defn ,name ,;more)
  ~(put all-fns ,(keyword name) ,name))

Wrapping it with do is problematic because I still want my-fn to be scoped to the top-level. Also, trying to use splice still creates a tuple of values.

As a background, I'm trying to learn the macro system by making a library that can be used as either a macro or a series of functions. The macro version will compile a series of sexps into a string literal at execution time, while there is an equivalent function for each that can be called to return a single string, like this:

(lib/compile
  (foo)
  (bar)
  (baz))

(string/join
  (lib/foo)
  (lib/bar)
  (lib/baz))
bakpakin commented 5 years ago

Is there a way of getting back more than one top-level form from defmacro? I think Clojure and Racket have this feature.

Clojure certainly cannot do this, at least without reader macros, if I am understanding this correctly. https://stackoverflow.com/questions/15796831/clojure-macro-to-return-two-or-more-s-expressions. Racket may, but Janet is unlikely to as we don't have multiple return values (nor do I plan on adding them). Multiple return values are a headache, and cause more problems than they solve, especially when you have proper destructuring of tuples.

Wrap it in a do and use the functions defglobal and varglobal to define your functions. These will define things at the top scope.

(do
  (defglobal 'thing "abcdefg")
  (defglobal 'my-fn "docstring... " (fn 'my-fn [] ...))
  (defglobal 'my-fn2 "docstring... " (fn 'my-fn2 [] ...)))
bakpakin commented 5 years ago

As for splicing, that won't work here. Splice only works as an argument to a function call or inside a quasiquote. Splice as a top level form, or not as an argument to a function will do nothing and act as the identity function. Future version may even raise a compilation error.

Inc0n commented 1 year ago

What's the difference between defn and defglobal? Seeing that defglobal would be able to create top level binds whilst defn can't within do block.

Inc0n commented 1 year ago

Hmm, it looks like defn can be nested within defns, and the scope will limit what is accessible from outside.