clojurescript interop problems

226 views
Skip to first unread message

Jack Moffitt

unread,
Jul 31, 2011, 11:00:44 PM7/31/11
to clo...@googlegroups.com
I'm having some trouble attempting to use interop with ClojureScript.
I'm trying to translate examples from the Closure book into
ClojureScript, and I keep getting stuck on various things.

1) When using goog.testing, it appears that I can't access
goog.testing.TestRunner and goog.testing.TestCase unless those
specific namespaces are imported as well. For example:

(ns example.core
(:require [goog.testing :as testing]))

(defn ^:export start-test []
testing/TestCase))

If i call start_test() from the browser repl it returns null. If I add
[goog.testing.TestCase :as tc] to the requires, without changing
start-test, then the code works fine.

2) Trying to launch the TestRunner fails if i do it in a function, but
works if I return the runner and call it from the browser.

(defn ^:export start-test []
(let [runner (testing/TestRunner.)
test (testing/TestCase. "foo")]
(.add test (tc/Test. "test-html-escaping"
test-html-escaping
goog.global))
(.initialize runner test)
((.execute runner))))

This will throw an exception that the TestRunner is not initialized.
However, if I change ((.execute runner)) to runner, then call
"start_test().execute()" it runs perfectly. I can only guess that the
this object is not getting set correctly for some reason.

Note that changing the last line to (. runner (execute)) also works.

3) Is there some way to export a symbol to be global? For example,
goog.testing.jsunit will automatically search the global namespace for
test functions, but I can't figure out (aside from js*) how to stick
something in there. I basically created start-test above to get
around this.

jack.

Rich Hickey

unread,
Aug 1, 2011, 9:12:22 AM8/1/11
to clo...@googlegroups.com

On Jul 31, 2011, at 11:00 PM, Jack Moffitt wrote:

> I'm having some trouble attempting to use interop with ClojureScript.
> I'm trying to translate examples from the Closure book into
> ClojureScript, and I keep getting stuck on various things.
>
> 1) When using goog.testing, it appears that I can't access
> goog.testing.TestRunner and goog.testing.TestCase unless those
> specific namespaces are imported as well. For example:
>
> (ns example.core
> (:require [goog.testing :as testing]))
>
> (defn ^:export start-test []
> testing/TestCase))
>
> If i call start_test() from the browser repl it returns null. If I add
> [goog.testing.TestCase :as tc] to the requires, without changing
> start-test, then the code works fine.
>

Yes, you'll have to :require all the components/files of a 'namespace'
and 'class' in order to use them from ClojureScript with Clojure-like
idioms. Since TestCase is in a different file, you need to require it
as well. You have to look at each lib to see the approach it takes,
e.g. some function oriented libs are self contained while 'class-like'
ones have a file per 'class'.

> 2) Trying to launch the TestRunner fails if i do it in a function, but
> works if I return the runner and call it from the browser.
>
> (defn ^:export start-test []
> (let [runner (testing/TestRunner.)
> test (testing/TestCase. "foo")]
> (.add test (tc/Test. "test-html-escaping"
> test-html-escaping
> goog.global))
> (.initialize runner test)
> ((.execute runner))))
>
> This will throw an exception that the TestRunner is not initialized.
> However, if I change ((.execute runner)) to runner, then call
> "start_test().execute()" it runs perfectly. I can only guess that the
> this object is not getting set correctly for some reason.
>
> Note that changing the last line to (. runner (execute)) also works.
>

(.execute runner) currently returns the value in the 'execute' slot of
runner, that value being a function object. Wrapping it in parens then
calls that function with no arguments (and thus no target object, i.e.
this) and cannot work.

(. runner (execute)) disambiguates the slot and forces it to be
considered as a method, i.e. a call. On the JVM, methods are not first-
class functions in fields, and we can use the type system to determine
if (. target member) (with no arguments) is a method call or a field
access. In JS, it is always potentially either, and there is no way
with a single syntax to determine which interpretation is desired,
thus the need to disambiguate.

(. target (method)) works, but is awkward, and, since it is so
infrequently used in Clojure proper, is not idiomatic.

One alternative is to interpret (. obj member), and thus (.member obj)
as a call always, but that begs the question as to field/slot access.
Something new will be required, and it will have to be added to
Clojure to allow for portable code. Changing Clojure was out of scope
for the first version, but I'd like to improve this ASAP.

One option is (. target :slot), possibly with the not-so-great (.:slot
target) as well.

Thoughts everyone?

> 3) Is there some way to export a symbol to be global? For example,
> goog.testing.jsunit will automatically search the global namespace for
> test functions, but I can't figure out (aside from js*) how to stick
> something in there. I basically created start-test above to get
> around this.

There is no global namespace in ClojureScript, but you can use
goog.global as a proxy (with some restrictions) for the JS global ns.
I'm not familiar with the requirements of goog.testing.jsunit, but
have you tried putting tests in goog.global. i.e.

(set! goog.global.mytest my-test)

Rich

Arthur Edelstein

unread,
Aug 1, 2011, 10:54:40 AM8/1/11
to Clojure
> One option is (. target :slot), possibly with the not-so-great (.:slot  
> target) as well.

Why not simply (target :slot) and (:slot target) as one means of
accessing field values. Then you can still have clojure's traditional
(.x target) execute when x is a function object and return x's value
otherwise. If you want to get a member function object instead of
executing it, use (:method target).

These two syntaxes seem to stay consistent with traditional clojure
but also accommodate javascript's equivalence between objects and
maps.

Arthur

Laurent PETIT

unread,
Aug 1, 2011, 11:41:30 AM8/1/11
to clo...@googlegroups.com


2011/8/1 Arthur Edelstein <arthure...@gmail.com>

> One option is (. target :slot), possibly with the not-so-great (.:slot  
> target) as well.

Why not simply (target :slot) and (:slot target) as one means of

(:slot target) would do what, when facing an object which implements the Associative interface ?
 
accessing field values. Then you can still have clojure's traditional
(.x target) execute when x is a function object and return x's value
otherwise. If you want to get a member function object instead of
executing it, use (:method target).

These two syntaxes seem to stay consistent with traditional clojure
but also accommodate javascript's equivalence between objects and
maps.

Arthur

--
You received this message because you are subscribed to the Google
Groups "Clojure" group.
To post to this group, send email to clo...@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+u...@googlegroups.com
For more options, visit this group at
http://groups.google.com/group/clojure?hl=en

Arthur Edelstein

unread,
Aug 1, 2011, 3:13:14 PM8/1/11
to Clojure
> > > One option is (. target :slot), possibly with the not-so-great (.:slot
> > > target) as well.
>
> > Why not simply (target :slot) and (:slot target) as one means of
>
> (:slot target) would do what, when facing an object which implements the
> Associative interface ?

Here's what I was trying to say. Assume that target is a javascript
object.

For the colon syntax:
If member is a field, then
(:member target) would return the value of the field.
If member is a method, then
(:member target) would return a function object (that is, the value of
the member field, which is a function), and
((:member target)) would execute the function.

This is sensible because javascript-style objects are associative
arrays.

For the dot syntax:
If member is a method, then
(.member target) would execute the function.
If member is a field, then
(.member target) would return the value of the field.
This is entirely consistent with clojure's existing syntax.

My feeling is this is the smallest possible perturbation to clojure's
behavior. The dot notation semantics don't change at all, and the
colon syntax is also essentially the same, with the handy addition
that because javascript contains first-class functions, you can treat
javascript objects as Associatives, whereas in general you can't treat
Java objects as associatives.

Arthur

Laurent PETIT

unread,
Aug 1, 2011, 3:19:10 PM8/1/11
to clo...@googlegroups.com


2011/8/1 Arthur Edelstein <arthure...@gmail.com>

Wasn't Rich trying to come up with a solution which could be retrofitted into Clojure ?
 

Arthur Edelstein

unread,
Aug 1, 2011, 3:45:27 PM8/1/11
to Clojure
> Wasn't Rich trying to come up with a solution which could be retrofitted
> into Clojure ?

I was trying to see how to avoid having to change anything in Clojure
proper. In the strategy I'm humbly suggesting, the syntax from Clojure
could work as-is. Just use same the dot notation (as in Clojure, for
executing methods and getting the value of non-function fields), and
if you need a member function as a function object (in ClojureScript)
just access it as you would a member of an IMap, using (:methodName
target) or (get target :methodName). Or, instead of keywords, you
could use strings, such as (get target "methodName").

Arthur

Arthur Edelstein

unread,
Aug 1, 2011, 4:05:04 PM8/1/11
to Clojure


On Aug 1, 12:45 pm, Arthur Edelstein <arthuredelst...@gmail.com>
wrote:
> > Wasn't Rich trying to come up with a solution which could be retrofitted
> > into Clojure ?
>
> I was trying to see how to avoid having to change anything in Clojure
> proper. In the strategy I'm humbly suggesting, the syntax from Clojure
> could work as-is. Just use same the dot notation (as in Clojure, for
> executing methods and getting the value of non-function fields), and
> if you need a member function as a function object (in ClojureScript)
> just access it as you would a member of an IMap, using (:methodName
> target) or (get target :methodName).

Sorry, not an IMap, an ILookup.
Reply all
Reply to author
Forward
0 new messages