clojure-dev

dmiller 2025-03-16T13:55:43.947809Z

The doc string for gen-interface states: > When compiling, generates compiled bytecode for an interface with > the given package-qualified :name (which, as all names in these > parameters, can be a string or symbol), and writes the .class file > to the compile-path directory. When not compiling, does nothing. That was true when gen-interface was introduced. The body was

(when *compile-files*
    (let [options-map (apply hash-map options)
          [cname bytecode] (generate-interface options-map)]
      (clojure.lang.Compiler/writeClassFile cname bytecode)))) 
Subsequently, specifically in commit 77173bb (2009.11.3) the code was changed to render the comment incorrect -- demonstrably it does something when not compiling, namely it defined the interface class in the running environment. That remains true -- the last revision of this code was commit 9f277c8 (2015.01.10) re CLJ-979. I wasn't sure if it was worth polluting http://ask.cljure.org if there is no interest in revision, especially given that I might be the only person to read this comment and note the discrepancy in the last 16 years.

๐Ÿ‘€ 1
dmiller 2025-03-17T14:13:18.845439Z

To answer my own questions. (And with apologies for not waiting 24 hours until I worked it out.) 1. Is there a reason gen-interface was written that way? Yes. 2. Could use regular function? No. 3. Other examples? Yes. The crux of the issue is pointed at by some examples: gen-interface, definterface (indirectly via gen-interface ), defprotocol, gen-class , proxy . Some of these are obvious, such as gen-interface. Others you have t olook for; You can hide side-effects behind any ~ in backquote form. These have a common side-effect: All generate .class files. Moreover, if one views macroexpansion as part of syntactic analysis and expand the scope of investigation, we can add fn* and deftype* . Processing those special forms during parsing has side-effects. FnExpr.Parse calls ObjExpr.Build . NewInstanceExpr.DeftypeParser.Parse and NewInstanceExpr.ReifyParse.Parse each also call (indirectly) ObjExpr.Build. ObjExpr.Build has the side-effect of generating a .class file. (And we can expand the list yet again. For example, defrecord macroexpands to include a deftype*, so it gets its .class file generation through the backdoor, so to speak.) The main distinction here is that fn* and deftype* are special forms and can hide their side-effects in the AST tree construction process. The others are macros that use macroexpansion for generating side-effects. The problem that brought me here has to do with re-establishing AOT-compilation for ClojureCLR for .NET 9 and later, given the constraints posed by .NET's solution to saving assemblies. Given the limited audience for that project, I'll stop here. Wish me luck.

๐Ÿ‘€ 1
dominicm 2025-03-17T14:16:35.505339Z

I also noticed this, and concluded I just didn't understand the docstring. But I didn't ask, either.

2025-03-17T15:01:35.705169Z

so is the docstring incorrect now?

dmiller 2025-03-17T15:23:50.794539Z

It's a minor point. gen-interface does do something when not compiling.

dmiller 2025-03-16T14:27:16.324099Z

I do have a question about gen-interface . gen-interface is a macro. However, it expands into a java.lang.Class object, namely, the class of the interface that was just defined. The macro expansion has side-effects. My working assumption to date has been that macros should be primarily syntactic operations, code-to-code transformations, and not have serious semantic side-effects during expansion. Here we have a macro that on expansion has significant side-effects and returns something trivial as the expansion. 1. Is there a reason that gen-interface was written that way? I think it could easily have expanded into the code that it is executing. Am I missing some weird issue that prevent this? 2. Alternatlively, why not write it as a regular function instead of a macro? It is only used in definterface and emit-protocol . Might requires some extra quoting of arguments to gen-interface in their expansions, so maybe that's reason enough. But it doesn't answer the question above.j 3. Are there other examples in the core Clojure code that has side-effecting macro expansions? I'm not suggesting any change. It's just in my current attempt to get AOT-compilation running again over on ClojureCLR, I have to do double evaluation during compilation, once into the compilation context, once into the running environment. Side effects during macroexpansion and an expansion into nothing useful really screws things up. I can tell users not to do things like that in their code, but can't avoid dealing with it if that's the was the Clojure core code is written. I'm happy enough to rewrite gen-interface over in ClojureCLR; I'm just trying to make sure I'm not missing some important reason gen-interface was written this way.

athos 2025-03-17T02:04:36.342629Z

FWIW, I also found a similar description in the defprotocol docstring a while ago and filed an https://clojure.atlassian.net/jira/software/c/projects/CLJ/issues/CLJ-1337 for that. Havenโ€™t heard anything since then, though.

dmiller 2025-03-17T02:27:57.720479Z

Referring to "defprotocol is dynamic, has no special compile-time effect, and defines no new types or classes." ?

athos 2025-03-17T02:40:17.683069Z

yes