Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

V1: Remove templating; consolidate to core.clj{s} #85

Merged
merged 9 commits into from
Oct 14, 2014
Merged

V1: Remove templating; consolidate to core.clj{s} #85

merged 9 commits into from
Oct 14, 2014

Conversation

cpetzold
Copy link
Contributor

This pull request proposes significant changes of the dommy API and internal structure. We have decided to remove the templating features of dommy in order to keep dommy small and simple to use and maintain. While dommy's templating has been useful in the past, removing it from the library vastly simplifies the internal namespace structure and resolves some outstanding issues.

From what we have seen at Prismatic and around the community, dommy's core value is in DOM event binding, query selection, and getting & setting element attributes. While we feel that there are plenty of alternative templating solutions available today, if anyone would like to maintain these features in a separate repository, please let us know.

Edit: The templating features have been pulled into this project: https://github.com/jeluard/hipo/

Changes

  • Updated ClojureScript, lein-cljsbuild and clojurescript.test dependencies
  • Removed all templating-related code (node, deftemplate, etc.), and removed dommy.template ns
  • Functions no longer coerce "node-like" parameters to dom nodes
  • Moved all functions from dommy.attr to dommy.core
  • Renamed dommy.macros to dommy.core, allowing for:
(ns foo
  (:require [dommy.core :as dommy :include-macros true]))
  • Renamed dommy.core/ancestor-nodes to dommy.core/ancestors
  • Added dommy.core/create-element and dommy.core/create-text-node to make up for removing node
  • Moved implementation-specific code to webapp.utils (for both clj and cljs)

loganlinn added a commit that referenced this pull request Oct 14, 2014
V1: Remove templating; consolidate to core.clj{s}
@loganlinn loganlinn merged commit 47b38ee into master Oct 14, 2014
@loganlinn loganlinn deleted the v1 branch October 14, 2014 18:38
@p-b-west
Copy link

These changes will break the sample project in simple-brepl, which is how I was introduced to dommy. The fix should be easy with the new api.

I notice that you now have common namespaces for core and utils in both cli and cljs. That clarifies things greatly. However, I see that as-str is still invoked within defmacro in a few places, and I still wonder why functions like this need to be resolved both at compile time and at runtime, requiring that the definition be repeated in cli and cljs. If the cljs function must be executed when the macro is invoked at runtime, why can it not be simply quoted in the defmaco, saving the trouble of the double definition?

@cpetzold
Copy link
Contributor Author

@numinasthmatic the idea behind calling as-str in both the macro and runtime versions is that we get a performance gain, not having to coerce the constants passed in at runtime. The compiled javascript is also much easier to read and understand.

In by-id for instance, we want (by-id :foo) to compile directly to document.getElementById("foo") as opposed to document.getElementById(dommy.utils.as_str(new cljs.core.Keyword(null, "foo", "foo", 4493897459))).

I agree that it's unfortunate to have duplicate code in utils.clj{s}, so we may think about using cljx to reduce the repetition if it gets out of hand.

@loganlinn
Copy link
Member

These changes will break the sample project in simple-brepl, which is how I was introduced to dommy. The fix should be easy with the new api.

The simple-brepl project or any project using pre-1.0.0 versions of dommy will continue to work as they always have because the jars will remain available. If members of the community still wish to use dommy's templating features going forward, we are willing to hand these features off to them to maintain as separate project

@p-b-west
Copy link

@cpetzold "we want (by-id :foo) to compile...to document.getElementById("foo") as opposed to document.getElementById(dommy.utils.as_str(new cljs.core.Keyword(null, "foo", "foo", 4493897459)))"

This is what I'm afraid I cannot understand. To get (by-id :foo) to compile to document.getElementById("foo"), try (by-id "foo"). That is, when you know at compile time the exact element you want to convert, you don't need to run any programmatic transformations.

If you want to compile (by-id ) you have to provide for the resolution of the variable into a string at runtime, because you don't know what it will be until runtime. Somewhere, at runtime, you have to perform the equivalent operation to as-str at runtime, do you not?

Runtime is JS time, because that's the VM you're running on. Can you see why I'm struggling with this?

@loganlinn
Copy link
Member

Can you see why I'm struggling with this?

I think I do. It seems to be here:

If you want to compile (by-id ) you have to provide for the resolution of the variable into a string at runtime, because you don't know what it will be until runtime. Somewhere, at runtime, you have to perform the equivalent operation to as-str at runtime, do you not?

If we know everything at compile time, ie the macro was passed literal values, then the macro can output code that doesn't even call the CLJS version. It will be compiled directly to JS interop code. The point is to bypass that code path at runtime, given our knowledge at compile time.

@p-b-west
Copy link

I'm getting there. Here was the misconception I had.

I thought of defmacro as being resolved only once into a sort of template into which substitutions would be made which replaced unresolved symbols with their expansion. I was aware that the arguments to defmacro were simply unresolved symbols, so I got this broken idea of a passive template being tucked away by the compiler when defmacro was first encountered by the compiler.

So I supposed there was some sort of pre-processed intermediate textual representation of the macro definition being held by the compiler. When the macro name was encountered in the text by the compiler, the arguments were substituted into the templates (or not, as in the case of an incorrectly quoted argument name) and that result was rescanned but the compiler.

I'm ashamed to admit this now, because my thinking was so woolly, but I was completely stupid about code that did not generate textual output.

For instance,

(defmacro by-id
  [id]
  (let [id (-> id utils/as-str (str/replace #"#" ""))]
    `(js/document.getElementById ~id)))

In this definition, the initial let doesn't generate any text for substitution, so I imagined, without being able to put my finger on it, that such "non-productive" code somehow survived and made its way into the final code output when invoked in the context of a call to the macro. It felt like squeezing a lemon pip when tried to puzzle this out. What I needed was a sequence diagram for the definition and resolution of macros, but I couldn't find one. It didn't help that I wasn't sure about the way the clojure macros interacted with the clojurescript program code.

The penny dropped for me when I realised that defmacro is active; it is resolved anew in its entirety each time the macro name is encountered. When this occurs, the let sees and resolves the symbol that has been passed in, and the clojurescript text that is generated is given the resolved, unquoted value of the transformed id. I think that a better way to write the defmacro to make this clearer would be (assuming I have this right now)

(defmacro by-id
  [id]
  (let [str-id (-> id utils/as-str (str/replace #"#" ""))]
    `(js/document.getElementById ~str-id)))

For experienced lispers, this would all be second nature. I used CPP for years in C code, and, in the distant mists of times past, I even wrote m4 macros for list processing. CPP doesn't help here at all, but had my m4 memories been fresh, I may not have had so much trouble. But CPP and m4 both operate on "foreign" text that is just passing through; lisp and clojure operate in themselves (lisp or clojure) on themselves.

That's homoiconicity, that's code as data.

I'm detailing this in the hope it might be useful to someone else who stumbles across it while trying to fathom these "active" macrodefs.

@p-b-west
Copy link

Thinking about this a bit more, it seems to me that there should be two as-str functions: one for use in defmacro (that is, for use in the compile phase) and one for use at runtime. That is another way to reduce confusion. The calls to as-str in defmacro cannot be accessed at runtime, by definition. They must disappear during compilation.

Having mac-as-str in the .clj file, and reserving as-str in the .cljs file for runtime use makes the separation of function clearer.

It seems to me that this sort of macro is inherently dangerous.

If I define a var like so
(def kvar :kword)
then call the by-id macro, in the initial string conditioning as-str call, the var will resolve to the keyword, and do what is expected.

However, if I have such a var, to which I intend to bind at runtime a keyword, then the initial phase will not resolve to a keyword, and the conditioning will disappear. If I subsequently bind a keyword value to the var, then call the macro, the conditioning will not occur, and the keyword will be passed through unchanged. I think.

Within a tightly-defined library of limited size, the programmer can keep an eye on things. But is your macro escaped into the wild, you would no longer have any influence on what arguments were being passed in, and no control over the compile-time environment.

I may be wrong about this (as I was earlier) but that seems to be a real risk of this type of macro definition.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants