Clojure

deftype generates new types when evaluation is expected to be suppressed

Details

  • Type: Defect Defect
  • Status: Closed Closed
  • Priority: Minor Minor
  • Resolution: Declined
  • Affects Version/s: Release 1.8
  • Fix Version/s: None
  • Component/s: None
  • Labels:
  • Environment:
    macOs 10.12.6

Description

There does not seem to be a consistent way to prevent deftype from emitting a new type. This was discovered in ClojureCLR when trying to write a defonce-style macro wrapper for deftype, but it affects ClojureJVM as well. It seems to emit types on analysis, which defies Clojure's evaluation semantics.

The following REPL session highlights expected and unexpected behavior, namely that when false is unable to prevent deftype from emitting a new type into memory. comment seems to work, however.

nREPL server started on port 52921 on host 127.0.0.1 - nrepl://127.0.0.1:52921
REPL-y 0.3.7, nREPL 0.2.12
Clojure 1.8.0
Java HotSpot(TM) 64-Bit Server VM 1.8.0_60-b27
    Docs: (doc function-name-here)
          (find-doc "part-of-name-here")
  Source: (source function-name-here)
 Javadoc: (javadoc java-object-or-class-here)
    Exit: Control+D or (exit) or (quit)
 Results: Stored in vars *1, *2, *3, an exception in *e

user=> (deftype TypeTest [a b])
user.TypeTest
user=> (->> user.TypeTest .getDeclaredFields (map #(.getName %)))
("a" "b")
user=> (deftype TypeTest [a b c])
user.TypeTest
user=> (->> user.TypeTest .getDeclaredFields (map #(.getName %)))
("a" "b" "c")  ;; <= expected redefinition 
user=> (when false (deftype TypeTest [a]))
nil
user=> (->> user.TypeTest .getDeclaredFields (map #(.getName %)))
("a") ;; <= when false fails to prevent redefinition
user=> (comment (deftype TypeTest [a b c]))
nil
user=> (->> user.TypeTest .getDeclaredFields (map #(.getName %)))
("a")  ;; <= comment manages to prevent redefinition
user=>

Activity

Hide
Kevin Downey added a comment -

not on analysis, on compilation.

deftype has a compilation time effect. similarly def has a compilation time effect.

so

(when false (def a 1))

will result in the var for a being interned, but it won't have a root value.

the reason wrapping in (comment ...) stops whatever from happening is compilation happens after macroexpansions, and the comment macro replaces whatever form with nil.

I am not sure this would be considered a bug. The only way to avoid this would be to have deftype generate types at runtime instead of at compile time, but I am pretty sure the types are generated at compile time to 1. avoid generating a new type every time the compiled expression is run 2. to make class generation fairly predictable so clojure can be used in environments with unusual classloader restrictions.

Show
Kevin Downey added a comment - not on analysis, on compilation. deftype has a compilation time effect. similarly def has a compilation time effect. so
(when false (def a 1))
will result in the var for a being interned, but it won't have a root value. the reason wrapping in (comment ...) stops whatever from happening is compilation happens after macroexpansions, and the comment macro replaces whatever form with nil. I am not sure this would be considered a bug. The only way to avoid this would be to have deftype generate types at runtime instead of at compile time, but I am pretty sure the types are generated at compile time to 1. avoid generating a new type every time the compiled expression is run 2. to make class generation fairly predictable so clojure can be used in environments with unusual classloader restrictions.
Hide
Alex Miller added a comment -

As Kevin commented, this is the intended behavior.

Show
Alex Miller added a comment - As Kevin commented, this is the intended behavior.
Hide
Ramsey Nasser added a comment -

Noted. compile-if is what we need. Apologies for the noise.

Show
Ramsey Nasser added a comment - Noted. compile-if is what we need. Apologies for the noise.

People

Vote (0)
Watch (0)

Dates

  • Created:
    Updated:
    Resolved: