Groups keyboard shortcuts have been updated
Dismiss
See shortcuts

Extending protocol to an existing JavaScript type

933 views
Skip to first unread message

Scott Nelson

unread,
Sep 3, 2015, 7:47:31 AM9/3/15
to ClojureScript
Why is this bad practice? I'm trying to extend a protocol to js/Function and I get the following warning:

WARNING: Extending an existing JavaScript type - use a different symbol name instead of js/Function

I'm trying to create a function that is polymorphic based on the input type (could be a function, map or vector) and I thought protocols were the best way to do that. Should I instead create a multimethod that dispatches on (type arg)?

Thanks!

-Scott

Gary Verhaegen

unread,
Sep 3, 2015, 7:49:58 AM9/3/15
to clojur...@googlegroups.com
Coukd you please post the code that produces that warning? Ideally a minimal case.
--
Note that posts from new members are moderated - please be patient with your first post.
---
You received this message because you are subscribed to the Google Groups "ClojureScript" group.
To unsubscribe from this group and stop receiving emails from it, send an email to clojurescrip...@googlegroups.com.
To post to this group, send email to clojur...@googlegroups.com.
Visit this group at http://groups.google.com/group/clojurescript.

Scott Nelson

unread,
Sep 3, 2015, 7:58:48 AM9/3/15
to ClojureScript
(defprotocol Foo
(foo [arg]))

(extend-protocol Foo
js/Function
(foo [arg]
(println arg)))

Francis Avila

unread,
Sep 3, 2015, 1:38:16 PM9/3/15
to ClojureScript
Extending a protocol to one of the global JS objects is bad for the same reason it is bad in Javascript: you are modifying a global object. Your extend-protocol is like saying Function.prototype.FOO_foo = function(this, arg){...} in javascript. (The "FOO_foo" is a long namespaced and mangled property name, but you are still modifying the global.)

You can extend "function" (and "object", "number", "string", "array", and "boolean") instead of js/Function (or js/Object, etc). This will implement the protocol from the "outside" (doing type checks on its argument) instead of assigning a new member to the global's prototype. E.g.:

(extend-protocol Foo
function
(foo [this] (println this)))

I'm not sure if you also need to extend IFn: I think "function" means only native js functions and not clojurescript objects implementing IFn. (Test with a multi-arity cljs function to verify.)


You can also extend the "default" type, which means everything will implement the protocol that doesn't have a more specific implementation.

I don't know where these magic protocol type names are documented.

Scott Nelson

unread,
Sep 3, 2015, 4:17:21 PM9/3/15
to ClojureScript
Thanks- that worked perfectly. I did not need to extend IFn and I actually got a runtime error when I tried (perhaps because IFn is a protocol in ClojureScript?). What are "function"/"object"/ect. in this case, syntactically? Is there any documentation about this?

Thanks again!

-Scott

Shaun LeBron

unread,
Sep 4, 2015, 1:28:29 PM9/4/15
to ClojureScript
The "function", "object", "array" symbols are just symbols that have meaning in the context of the `extend-type` macro, so nothing special syntactically. They just map internally to the type strings produced by `goog/typeOf`, used for protocol dispatch.

Just added some documentation to the `extend-type` doc:
https://github.com/cljsinfo/cljs-api-docs/blob/catalog/refs/cljs.core_extend-type.md

Scott Nelson

unread,
Sep 4, 2015, 1:42:46 PM9/4/15
to ClojureScript
OK, thanks. Great documentation by the way. Definitely going to refer to this from now on.

Yehonathan Sharvit

unread,
Sep 6, 2015, 8:01:02 AM9/6/15
to ClojureScript
On Friday, 4 September 2015 20:42:46 UTC+3, Scott Nelson wrote:
> OK, thanks. Great documentation by the way. Definitely going to refer to this from now on.

Francis,

Do you have an ide yhy the implementation of `extend-type` for:
1. js types modifies the js type itself
2. while for cljs types it modifies the protocol (from the outside)?

Shaun LeBron

unread,
Sep 6, 2015, 12:27:56 PM9/6/15
to ClojureScript
I just looked at the implementation, and it's not done 'from the outside' for cljs types. `extend-type` adds protocol methods to the type object's prototype. Only exception is for JS base types (e.g. "function", "number", "array"). You can see the effects in this gist:
https://gist.github.com/shaunlebron/a98a05b47a1521b58a6b

Not sure why though

David Nolen

unread,
Sep 6, 2015, 2:05:13 PM9/6/15
to clojur...@googlegroups.com
It's not safe to change the JavaScript base types directly - besides the obvious down sides of global changes to types you don't control, this often triggers dramatic de-optimization in JavaScript engines.

David

Thomas Heller

unread,
Sep 7, 2015, 7:55:55 AM9/7/15
to ClojureScript
For a little JavaScript WTF and one reason why things are the way the are:

Number.prototype.whatAmI = function() { return typeof(this); };
var x = 1;
x.whatAmI();
"object"
typeof(x)
"number"

Fun times debugging that ...

Cheers,
/thomas

Marc Fawzi

unread,
Sep 7, 2015, 2:49:11 PM9/7/15
to clojur...@googlegroups.com
thomas,

<<
Number.prototype.whatAmI = function() { return typeof(this); };
var x = 1;
x.whatAmI();
"object"
typeof(x)
"number"
>>

var x = 1  .... is the same as ... var x = Number(1) 

So in both cases x.whatAmI is going to refer to Number.prototype which is indeed of type [object Number] or as typeof reports it as "object" while x is going to refer to the primitive type returned by Number() or as typeof reports it  as"number"

On the other hand, if you say:

var x = new Number(1) ....... then the 'this' in whatAmI will refer to an instance of Number which is also of type [object Number] which means 'typeof x' will be "object" not "number" and will be consistent (in its String representation) with the output of x.whatAmI(). 

In this case the primitive type passed to the Number constructor (i.e. 1) is going to be placed behind an accessor called 'valueOf()' so you can do x.valueOf() to get 1

The point I wanted to make is 'this' without 'new' will refer to the object the function is defined on be it 'window' or whatever object.

Intuitive, no? but I would not call this a WTF moment, although JS has so many....

:)  




Thomas Heller

unread,
Sep 7, 2015, 5:55:43 PM9/7/15
to ClojureScript

> The point I wanted to make is 'this' without 'new' will refer to the object the function is defined on be it 'window' or whatever object.
>

That is incorrect. "this" is the actual number, not window. new or not. Behavior is probably different for other types. ;)

Number.prototype.addFive = function() { return this + 5 };
var x = 1;
x.addFive(); => 6
var x = new Number(3)
x.addFive(); => 8

Anyways, we agree that JS is full of WTF. Let's enjoy our sweet sweet CLJS world. :)

Cheers,
/thomas

Marc Fawzi

unread,
Sep 7, 2015, 7:00:43 PM9/7/15
to clojur...@googlegroups.com
:) Indeed ... different than the usual way, see below for how "new" changes 'this' from being the object whatAmI is defined on to  

var T = function() { console.log('constructor called')}

T.prototype = {} 

T.prototype.whatAmI = function() { 
return {type: Object.prototype.toString.call(this), isEqualToPrototype: (this === T.prototype)}
}

var x = new T  // (x is instance of T) prints constructor called

x instanceof T

true

x.whatAmI()
//prints: {type: "[object Object]", isEqualToPrototype: false}

var y = T  (y is T)

y instanceof T

false

y.prototype.whatAmI()

Object {type: "[object Object]", isEqualToPrototype: true}

See?

WTF indeed, in case of primitive types. 

Maybe Brendan Eich can explain the primitive types stuff to me but last time I mentioned ClojureScript on webapps mailing list he unleashed a huge rant about how I should not speak of that which I don't understand. He was clearly triggered by mention of CLJS. Seems like it has gotten under his skin, especially with guys like Peter Hunt admitting that the React team has been borrowing idea from CLJS. Microsoft lead on IE was far more generous and open to discussing ideas around a thread safe version of the DOM... 




Cheers,
/thomas

Thomas Heller

unread,
Sep 8, 2015, 8:00:21 AM9/8/15
to ClojureScript
Hmm I think you have those confused.

Take the equivalent from Java:

class MyClass {
}

MyClass x = new MyClass();
Class y = MyClass.class;

or JS:

var MyClass = function() { /* ctor */ };
var inst = new MyClass()

inst != MyClass
inst.prototype == MyClass

inst is an instance of MyClass (prototype) but not equal to MyClass.

What is going on with Numbers is related to boxing, so there is a perfectly fine explanation but that doesn't make it less confusing when you first encounter it.

Enough of this, back to topic. Don't extend Native types. ;)

/thomas

Marc Fawzi

unread,
Sep 8, 2015, 10:37:12 AM9/8/15
to clojur...@googlegroups.com
Crossed context here.

What I was pointing to is that trying to create an instance without "new" means that 'this' in the prototype method whatAmI will point to the prototype itself rather than the instance. That is what the === proves in the example in my previous rely. I thought it was the problem in case of primitive types too like Number. It seems unclear to you what I'm trying to explain about the use of "new" to call the constructor vs assigning the constructor by reference to some var. I am not sure what the confusion is in my case aside from the primitive type case, so would love to learn if there is actually some confusion about the effect of new! I think it's important to understand how JavaScript works if you are programming in any language that compiles to it.




Sent from my iPhone

Marc Fawzi

unread,
Sep 8, 2015, 11:18:08 AM9/8/15
to clojur...@googlegroups.com
Continuing about the effect of "new" in JS since it is a subject of exploration/learning at this point, and I believe firmly in knowing your JS in order to master your CLJS, so maybe one day you could fix a bug in CLJS itself and be a contributor, which is hard to do in most cases if you don't know the underlying JS really well, and that's why I'm focusing on this point, so we understand the situation rather than dismiss it as immaterial. 

See console session below explaining effect of "new" in your original example and follow up one. I don't think this tells you anything you didn't know, but the latter one shows how 'this' would refer to instance if you use "new" but not if you don't use new, and I thought primitive types would work the same but you brought up 'boxing' and I'm reading up on it now: http://www.jisaacks.com/javascript-boxing/

Where is the confusion as far as the effect of "new" in the examples below...?

var MyClass = function() { /* ctor */ };

var inst = MyClass

inst instanceof MyClass

false   <----- because no "new"

var inst = new MyClass()

inst instanceof MyClass

true <----- because "new"

And repeating the previous example in my original re-reply:

var T = function() { console.log('constructor called')}

T.prototype.whatAmI = function() { 
return {type: Object.prototype.toString.call(this), isEqualToPrototype: (this === T.prototype)}
}

T.whatAmI()
var x = new T()  // (x is instance of T) prints constructor called

x instanceof T
prints: constructor called
prints: true

x.whatAmI()
prints: {type: "[object Object]", isEqualToPrototype: false}

var x = T

x.prototype.whatAmI()

prints: {type: "[object Object]", isEqualToPrototype: true}  <---- because 'this' in whatAmI now points to T.prototype

So for sure it is confusing but all of the above is verified in console. 

What is 'boxing' btw? :)  that's what I'd like to learn...

Marc Fawzi

unread,
Sep 8, 2015, 11:47:28 AM9/8/15
to clojur...@googlegroups.com
<<
var MyClass = function() { /* ctor */ };
var inst = new MyClass()

inst.prototype == MyClass

>>

"inst is an instance of MyClass (prototype) but not equal to MyClass."

Yes, no one argues with that. It is an instance of MyClass and it is obviously !== MyClass, 

But saying 'inst.prototype == MyClass' is not correct.

You've left it without comment so I just want to be sure it does not confuse anyone.

"Boxing" is the real take for me fro this thread, and it shines a light on how to work with primitive types.

The rest of it from my side can be summarized as follows:

var MyClass = function() { /* ctor */ };

var inst = MyClass

inst instanceof MyClass

false   <----- because no "new"
var inst = new MyClass()

inst instanceof MyClass

true <----- because "new"

And repeating the previous example in my original re-reply:

var T = function() { console.log('constructor called')}

T.prototype.whatAmI = function() { 
return {type: Object.prototype.toString.call(this), isEqualToPrototype: (this === T.prototype)}
}

T.whatAmI()
var x = new T()  // (x is instance of T) prints constructor called

x instanceof T
prints: constructor called
prints: true

x.whatAmI()
prints: {type: "[object Object]", isEqualToPrototype: false}

var x = T

x.prototype.whatAmI()

prints: {type: "[object Object]", isEqualToPrototype: true}  <---- because 'this' in whatAmI now points to T.prototype

....

If there's anything you think I'm confused about (aside from Boxing, which I've just learned about thanks to you!) please don't shy away from explaining. 

I've always believed in the constructivist approach to programming, i.e. to understand things from the very bottom, and in this case it is the JS stuff that is at the very bottom of CLJS...






/thomas

Thomas Heller

unread,
Sep 8, 2015, 12:45:30 PM9/8/15
to ClojureScript

>
> var x = T
> x.prototype.whatAmI()
> prints: {type: "[object Object]", isEqualToPrototype: true}  <---- because 'this' in whatAmI now points to T.prototype
>

The thing is that you are not supposed to call methods on the prototype directly.

Your code has this in it:

Object.prototype.toString.call(this)

which is something you usually do not do. The first argument to the .call function becomes "this" in the context of the function which is "cheating" and bypassing prototype inheritance. I consider .call equal to reflection in Java. You should be doing this.toString().

I cannot explain prototype inheritance well enough and will probably only cause more confusion at this point. [1] does a way better job than I ever could.

Cheers,
/thomas

[1] https://developer.mozilla.org/en-US/docs/Web/JavaScript/Inheritance_and_the_prototype_chain

Marc Fawzi

unread,
Sep 8, 2015, 2:47:42 PM9/8/15
to clojur...@googlegroups.com
<<
Your code has this in it:

Object.prototype.toString.call(this)

which is something you usually do not do. The first argument to the .call function becomes "this" in the context of the function which is "cheating" and bypassing prototype inheritance. I consider .call equal to reflection in Java. You should be doing this.toString().
>>

This is because some primitive types do not support toString 

undefined.toString()

VM188:2 Uncaught TypeError: Cannot read property 'toString' of undefined
    at <anonymous>:2:10
    at Object.InjectedScript._evaluateOn (<anonymous>:895:140)
    at Object.InjectedScript._evaluateAndWrap (<anonymous>:828:34)
    at Object.InjectedScript.evaluate (<anonymous>:694:21)(anonymous function) @ VM188:2InjectedScript._evaluateOn @ VM34:895InjectedScript._evaluateAndWrap @ VM34:828InjectedScript.evaluate @ VM34:694

Object.prototype.toString.call(undefined)
"[object Undefined]"

So it becomes a rule of thumb to use Object.prototype.toString rather than call .toString on the object and sometimes have JS throw an exception

See this:




Reply all
Reply to author
Forward
0 new messages