I'm hitting a bit of frustration with (defprotocol) and friends and
I'm hoping someone can enlighten me:

Context: this is part of the Cascade layer I'm building on top of
Compojure. It's all about rendering markup, borrowing ideas from
Tapestry.

I have a protocol that represents Assets: any kind of static resource
that can be exposed to the client via a URL, such as a JavaScript
library or stylesheet:

(defprotocol Asset
  "Represent a server-side resource so that it can be exposed
efficiently to the client."
  (^InputStream content [asset] "Returns the content of the Asset as a
stream of bytes, or null if the Asset does not exist.")
  (^String client-url [asset] "Returns an absolute URL to the Asset."))

Cascade builds a DOM tree before streaming; one step of that is to
convert attribute values inside DOM nodes into strings.

(defprotocol ToAttributeValueString
  "Converts an attribute value to a string. It is not necessary to
apply quotes (those come at a later stage)."
  (to-attribute-value-string [value]
    "Converts the value to a string that can be safely streamed as an
attribute value."))

(extend-protocol ToAttributeValueString
  String
  (to-attribute-value-string [string] (encode-string string))

  Number
  (to-attribute-value-string [num] (.toString num))

  Keyword
  (to-attribute-value-string [kw] (encode-string (name kw))))


Now, I want Assets to be used as attribute values, so:

(extend-type Asset
  ToAttributeValueString
  (to-attribute-value-string [asset] (:client-url asset)))

But this fails with:

Exception in thread "main" java.lang.IllegalArgumentException: Unable
to resolve classname: Asset, compiling:(cascade.clj:84)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6416)
        at clojure.lang.Compiler.analyze(Compiler.java:6216)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6397)
        at clojure.lang.Compiler.analyze(Compiler.java:6216)
        at clojure.lang.Compiler.analyze(Compiler.java:6177)
        at clojure.lang.Compiler$MapExpr.parse(Compiler.java:2782)
        at clojure.lang.Compiler.analyze(Compiler.java:6224)
        at clojure.lang.Compiler.analyze(Compiler.java:6177)
        at clojure.lang.Compiler$InvokeExpr.parse(Compiler.java:3503)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6411)
        at clojure.lang.Compiler.analyze(Compiler.java:6216)
        at clojure.lang.Compiler.analyze(Compiler.java:6177)
        at clojure.lang.Compiler$BodyExpr$Parser.parse(Compiler.java:5572)
        at clojure.lang.Compiler$FnMethod.parse(Compiler.java:5008)
        at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3629)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6407)
        at clojure.lang.Compiler.analyze(Compiler.java:6216)
        at clojure.lang.Compiler.eval(Compiler.java:6462)
        at clojure.lang.Compiler.load(Compiler.java:6902)
        at clojure.lang.RT.loadResourceScript(RT.java:357)
        at clojure.lang.RT.loadResourceScript(RT.java:348)
        at clojure.lang.RT.load(RT.java:427)
        at clojure.lang.RT.load(RT.java:398)
        at clojure.core$load$fn__4610.invoke(core.clj:5386)
        at clojure.core$load.doInvoke(core.clj:5385)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.core$load_one.invoke(core.clj:5200)
        at clojure.core$load_lib.doInvoke(core.clj:5237)
        at clojure.lang.RestFn.applyTo(RestFn.java:142)
        at clojure.core$apply.invoke(core.clj:602)
        at clojure.core$load_libs.doInvoke(core.clj:5271)
        at clojure.lang.RestFn.applyTo(RestFn.java:137)
        at clojure.core$apply.invoke(core.clj:604)
        at clojure.core$use.doInvoke(core.clj:5363)
        at clojure.lang.RestFn.invoke(RestFn.java:436)
        at main$eval3$loading__4505__auto____4.invoke(main.clj:1)
        at main$eval3.invoke(main.clj:1)
        at clojure.lang.Compiler.eval(Compiler.java:6465)
        at clojure.lang.Compiler.eval(Compiler.java:6455)
        at clojure.lang.Compiler.load(Compiler.java:6902)
        at clojure.lang.Compiler.loadFile(Compiler.java:6863)
        at clojure.main$load_script.invoke(main.clj:282)
        at clojure.main$script_opt.invoke(main.clj:342)
        at clojure.main$main.doInvoke(main.clj:426)
        at clojure.lang.RestFn.invoke(RestFn.java:408)
        at clojure.lang.Var.invoke(Var.java:401)
        at clojure.lang.AFn.applyToHelper(AFn.java:161)
        at clojure.lang.Var.applyTo(Var.java:518)
        at clojure.main.main(main.java:37)
        at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
        at 
sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:39)
        at 
sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:25)
        at java.lang.reflect.Method.invoke(Method.java:597)
        at com.intellij.rt.execution.application.AppMain.main(AppMain.java:120)
Caused by: java.lang.IllegalArgumentException: Unable to resolve
classname: Asset
        at clojure.lang.Compiler$HostExpr.tagToClass(Compiler.java:1003)
        at clojure.lang.Compiler.tagClass(Compiler.java:7878)
        at clojure.lang.Compiler$FnMethod.parse(Compiler.java:4955)
        at clojure.lang.Compiler$FnExpr.parse(Compiler.java:3629)
        at clojure.lang.Compiler.analyzeSeq(Compiler.java:6407)
        ... 53 more


What I eventually discovered was I had to qualify it as:

(extend-type cascade.Asset
  ToAttributeValueString
  (to-attribute-value-string [asset] (:client-url asset)))

>From my perspective, defprotocol appears to create a name (in the
current namespace) as well as a Java interface (the real type). It
feels to me like I should be able to pass either the interface or the
protocol into extend-type and have it Just Work. Is there some concern
I'm missing here? Thoughts?


-- 
Howard M. Lewis Ship

Creator of Apache Tapestry

The source for Tapestry training, mentoring and support. Contact me to
learn how I can get you up and productive in Tapestry fast!

(971) 678-5210
http://howardlewisship.com

-- 
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clojure@googlegroups.com
Note that posts from new members are moderated - please be patient with your 
first post.
To unsubscribe from this group, send email to
clojure+unsubscr...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Reply via email to