How can I keep the "type" of the template stable through several functions? clojure.lang.LazySeq becomes clojure.lang.PersistentVector

68 views
Skip to first unread message

lawrenc...@gmail.com

unread,
Dec 21, 2013, 5:49:56 PM12/21/13
to enliv...@googlegroups.com
The 2 types that are giving me trouble are: 

clojure.lang.PersistentVector

clojure.lang.LazySeq

Either one is fine with me, I just need the template to stay stable: one or the other type. 

I load the initial template like this: 

(defn get-html-file-as-seq-of-nodes [which-template]
  {:pre [(= (type which-template) java.lang.String)]
   :post [(= (type %) clojure.lang.LazySeq)]}
  (enlive/html-resource (str "templates/" (str which-template))))

This works fine: the template starts off as a lazyseq. I need for it to stay that way, or if there is an easy way to flip right here to clojure.lang.PersistentVector, that would be fine. 

I have then run the template through a long chain of functions, each of which makes some small change. 

This function dies on the post assert:

(defn tma-paginate-items [template request]
  {:pre [
         (= (type template) clojure.lang.LazySeq)
         (= (type request) clojure.lang.PersistentHashMap)
         ]
   :post [(= (type %) clojure.lang.LazySeq)]}  
  (enlive/transform template [:#tma-paginate-items]  
                    (enlive/clone-for [item (controller/paginate-results request)]
                                      [:.tma-paginated-list :a]
                                      (enlive/content (str (:item-name item) " | User: "(:user-item-name item) " | "(:created-at item)))
                                      [:.tma-paginated-list :a]
                                      (enlive/set-attr :href (str "/admin/show/" (:item-name item))))))

This function is now returning a clojure.lang.PersistentVector. 

Is there an easy way I can change the template to clojure.lang.PersistentVector before I start running it through my various transforming functions? 

 

Linus Ericsson

unread,
Dec 21, 2013, 6:48:34 PM12/21/13
to enliv...@googlegroups.com
Try with doall [1], but I really can't see why you would like to force the sequence to evaluate anyway.

Isn't it enough to check that the template is a seq? [2]
/Linus


2013/12/21 <lawrenc...@gmail.com>

--
You received this message because you are subscribed to the Google Groups "Enlive" group.
To unsubscribe from this group and stop receiving emails from it, send an email to enlive-clj+...@googlegroups.com.
To post to this group, send email to enliv...@googlegroups.com.
Visit this group at http://groups.google.com/group/enlive-clj.
For more options, visit https://groups.google.com/groups/opt_out.

lawrenc...@gmail.com

unread,
Dec 21, 2013, 7:47:24 PM12/21/13
to enliv...@googlegroups.com

>Isn't it enough to check that the template is a seq? [2]

The pre and post conditions are meant to indicate to other programmers that template/transform functions should take and return the same type. All of the template functions (in my app) should form a pipeline that can be threaded. I would prefer to enforce this strictly, though if there is no way to do this strictly, I will relax the constraint. But at the very least, I'd like to understand why I can not achieve what I'm aiming for (strictness). 

Linus Ericsson

unread,
Dec 22, 2013, 11:52:17 AM12/22/13
to enliv...@googlegroups.com
The internal structure of the incoming sequence of selected nodes is not really relevant here. I'll try to convince you why:

Enlive is a templating library. It's main function is insert incoming data into some pre-defined template as quickly as possible. The resulting data is most often printed to a java.io.*Writer in a java Servlet Response object.

Conceptually an enlive template consists of small nested Clojure hash-maps representing XML-tags and all their attributes. These tags also keeps their (stringy) :content and also a ordered sequence of :children nodes to this tag. This data structure is navigated with a suitable zipper, that allows Enlive to navigate the tag-tree and implement efficient selector state-machines to find the selected nodes of the tag-tree (≈DOM). (A zipper conceptually just defines properties for navigating up, and down, right and left in the data structure and whether a position is/has a branch or not. The zipper itself also contains a "bread crumbs" feature and can be flattened. Very, very suitable for navigating XML DOM trees.)

The cool thing with zippers is that they allow us to make reasonable efficient changes to the tree which are also immutable, largely by reusing to old immutable parts of the zipper not changed in the operation, similar to how clojures vector, [], and hash-map, {}, does.

When Enlive as done all the transformations to a template, Enlive simply traverses (flattens) the tag-tree zipper from the root-tag and prints the various elements string representations to the print-writer output.

The good thing with Enlive is that it, when compiled to byte-code and the java just-in-time compilation kicks in, large parts of the "finaled" string representations could be chunked, even though the representation in the template works through many layers of clojure data structures. Essentially the template could be compiled down to cpu optimized machine code, keeping a sane (but powerful) interface to the developer. How cool isn't that?!

One thing left to answer though. Why do you see lazy sequences or persistent vectors and not some zipper-like structure in the transform functions you like to test?

It turns out the root of the zipper is hidden form the transform functions we supply. The transform functions just gets the nodes matched by the supplied selector and it should return a transformed version of each node.

The length of the sequence of matching tags could vary depending on the template as well as its previous transforms and their arguments, but it could also be just too memory intensive to realize all the nodes selected, and the vector of realizations would be of no use for the transform functions anyway. For small collections a PersisentVector is likely most efficient, for larger results a LazySequence could be more efficient. It's just a question of implementation, and could be subject to change without warning in coming releases of Enlive if new, more efficient data structures would emerge.

A reasonable strictness on your function would probably be to ensure both the incoming nodes and the results are a seq? (it's actually just a plan java-interface [2], strict enough, isn't it?). You could also make sure that the transform returned nodes qualifies as Enlive tags or what is kept as in between them in the resulting HTML document.

/Linus



lawrenc...@gmail.com

unread,
Dec 22, 2013, 1:00:44 PM12/22/13
to enliv...@googlegroups.com

Your explanation is very good. This should be added to the wiki on Github for Enlive as a preparatory note for those who are looking into the code. I have been meaning to read the source code on Enlive and will do so over the holidays (I have noticed with Clojure (compared to Java) it is easier to read the source code of 3rd party libraries, perhaps because Clojure is a concise language, or perhaps the quality of the code tends to be so good). 

I tried (doall) as you suggested and that did not work. 

I will fall back to testing that input and output are sees. I suppose that is all that matters, as you've argued. 

Meikel Brandmeyer

unread,
Dec 21, 2013, 6:53:11 PM12/21/13
to enliv...@googlegroups.com
Hi,

Am 22.12.2013 00:48, schrieb Linus Ericsson:
> Try with doall [1], but I really can't see why you would like to force
> the sequence to evaluate anyway.
>
> Isn't it enough to check that the template is a seq? [2]
>
> [1] http://clojuredocs.org/clojure_core/clojure.core/doall
> [2] http://clojuredocs.org/clojure_core/clojure.core/seq_q

seq? will be false for vectors. The questions is why you want to tie
yourself to implementation details of the library? If you really want to
go for types you should probably use core.typed instead of post conditions.

Personal opinion: I find this type checking in a dynamic language like
clojure rather contra productive; creating problems were there aren't
any. This doesn't solve your problem, though.

Kind regards
Meikel Brandmeyer

--
Meikel Brandmeyer
Clojure Trainings
Kastellstraße 3
63526 Erlensee
http://kotka.de
USt.-Id: DE 285 667 417

signature.asc
Reply all
Reply to author
Forward
0 new messages