When is a function not a function?

T

Thomas 'PointedEars' Lahn

Matt said:
I must have missed the note in your documentation that says what you
can and can't pass to get accurate results. So you have to know what
something is ahead of time, before determining if it is callable?

That's ridiculous. Do you even recognize what you are saying?
Imagine a function that can take any type of input, and does different
things depending on what is passed in. You might need to determine if
the user has passed in a callable object or a document node.

That is where you are misguided. Such a function simply is not required
in reasonable programming.


PointedEars
 
M

Matt Kruse

That is where you are misguided. Such a function simply is not required
in reasonable programming.

That's a whole different point. One I made a while ago, in fact,
saying that such a requirement in an API may be bad design. Then
again, it may not. A function that takes a single argument and acts
"appropriately" no matter what kind of argument is passed to it is
extremely useful. Otherwise you'll end up with myfunc_string(a),
myfunc_number(a), myfunc_function(a), etc. That's a mess.

But that's irrelevant.

I was asking about whether it was really possible to determine if
something is callable in all cases, or if it was possible to get
pretty close. Your "solution" comes nowhere close.

Criticism has been made of the attempts by libraries like jQuery and
Dojo, but when you consider different cases where browsers return
questionable results from typeof, etc, then you can understand why
they use "weird" logic to try to really determine the best guess.

Matt Kruse
 
V

VK

It's already been said that there is probably no fool-proof way to
determine if something is truly callable.

Coming to you original request, the task was to make the program aware
of the current Firefox bug where OBJECT HTML element reported as
typeof "function" instead of "object". A truly right sequence would
actually be:
1) Report the bug at bugzilla.mozilla.org with the minimum demo case
like:
<script>
var foo = document.createElement('P');
var bar = document.createElement('OBJECT');
window.alert(typeof foo); // 'object'
window.alert(typeof bar); // 'function'
</script>
Such evident bugs caused by a simple typo in the name table are
usually being fixed in the next minor update.

2) Having referenced this bug in your port you would ask how to
overcome this bug for the time being in case of dealing with a 3rd
party library sending arguments to your methods.

In this case it could be suggested to relay for the next 2-3 months on
toString method of DOM Elements which is lucky not broken:

var foo = document.createElement('P');
var bar = document.createElement('OBJECT');
window.alert(typeof foo); // 'object'
window.alert(typeof bar); // 'function'
// but:
window.alert(foo); // 'object HTMLParagraphElement'
window.alert(bar); // 'object HTMLObjectElement'

On a wider run we have two types of bugs of this kind currently
exposed by some browsers:
1) non-callable objects reported by typeof as "function"
2) callable objects reported by typeof as "object"

One bug of this kind is just mentioned. One bug of the second type
would be say window.alert(typeof document.createElement); that gives
"object" for IE

First we are leaving out philosophical approaches of "covering each
and every possible bug on any browser on any platform in the past,
present and future".

Having made this - very painful for some but necessary step - on of
suggested workarounds could be to check toString() return values.
For the situation 1), exposed by Firefox, check if toString return
value contains "object HTML" substring.
For the situation 2), exposed by IE, check if toString return value
contains "function". On IE all DOM methods uniformly reported by
toString as
"function methodName() {
[native code]
}"
while non-callable DOM objects reported as "[object]".

So again: there is not any problem if someone is doing a real
practical task. If the subject is again about the "futility to try to
make a reliable program" and stuff the clj is not the best place for
it IMO. comp.programming is mush better for that.
 
V

VK

V

VK

So again: there is not any problem if someone is doing a real
practical task.

In the particular, in those rare cases when really needed - without
any claims that it is the absolutely best out of possible solutions:

function $typeof(obj) {
/* Firefox APPLET and OBJECT typeof bug
* workaround.
*/
if ('tagName' in obj) {
return 'object';
}
/* IE DOM methods bug workaround.
* IE DOM Elements do not support
* explicit toString method call
* ('no such property' error)
* so using implicit call instead.
*/
else if ( ('ActiveXObject' in window) &&
((''+obj).indexOf('function')!=-1) ) {
return 'function';
}
else {
return typeof obj;
}
}

Anyone is welcome to add as many extra checks as desired: the presence
of argument, of argument not being a primitive etc. A very important
thing to remember is that there is nothing for free in this world:
more paranoiac your method becomes - more protected it is from
possible misuse; but at the same time it gets slower for the normal
documented usage of your program. So for a real (not theoretical)
programming the task is not to put as many possible checks as one can
think of. The real task is to find the border line of a reasonable
misuse protection and after that let program die.

"If someone really wants to make the program die - so let him".
 
M

Matt Kruse

else if ( ('ActiveXObject' in window) &&

This is and always has been a terrible way to infer anything.
Use conditional compilation instead, which will only run in the
JScript engine where the "problems" exist.
((''+obj).indexOf('function')!=-1) ) {
return 'function';
}

This is way to broad. An object like
 
M

Matt Kruse

else if ( ('ActiveXObject' in window) &&

This is and always has been a terrible way to infer anything.
Use conditional compilation instead, which will only run in the
JScript engine where the "problems" exist.
((''+obj).indexOf('function')!=-1) ) {
return 'function';
}

This is way to broad. An object like
{ description:'This lets you test for a function'}
would be labeled a 'function' by your $typeof function.
The real task is to find the border line of a reasonable
misuse protection and after that let program die.

The real task is to figure out exactly how you want such a function to
operate and on which browsers. It is possible to approach a general
solution that works in all cases, but never reach it. By identifying
browser quirks with respect to host objects and accounting for them
you can cover most cases, which may be good enough. But some such
fixes need to be browser-specific and the code required to implement
it becomes complex.

I've got some improved code that I've been working on that uses
conditional compilation and the "russian doll" technique to unroll
itself to the most efficient function after a few tests have been run
the first time. So far I think it comes the closest to being accurate
of any approach I've seen so far. I'll post it for critique when I get
it done.

Matt Kruse
 
V

VK

This is and always has been a terrible way to infer anything.
Use conditional compilation instead, which will only run in the
JScript engine where the "problems" exist.

Unless someone will implement a browser with conditional compilation
and IE spoofing. :))

Taking into account the OTC deal of browser producers ("spoof whatever
you need but ActiveXObject") this property check is very reliable as
well - again on condition that someone is trying to use your program
and not to get a runtime error. There is no protection for a
determined user of the latter kind - but he/she never was a subject of
my preoccupations. Within the same OTC deal for IE/Gecko branches it
is pretty secure to:
if ('ActiveXObject' in window) {
// IE branch
}
else if ('GeckoActiveXObject' in window) {
// Gecko block
}
else if ('opera' in window) {
// Opera block
}
else {
// some lesser-minor UA
}

Again: no offence of any kind to the conditional compilation instead.
This is way to broad. An object like
{ description:'This lets you test for a function'}
would be labeled a 'function' by your $typeof function.

Are you sure about it? ;-) Do not mix an object property value with
toString of the object itself.
 
V

VK

Are you sure about it? ;-) Do not mix an object property value with
toString of the object itself.

What is bad in this block is that it will make unnecessary checks for
JScript objects where typeof always works just fine. Because in IE DOM
model DOM objects have nothing to do with JScript objects the
suggested optimization could be:

else if (('ActiveXObject' in window) &&
(!Object.prototype.isPrototypeOf(obj)) &&
((''+obj).indexOf('function')!=-1)) {
return 'function';
}
 
M

Matt Kruse

I've got some improved code that I've been working on that uses
conditional compilation and the "russian doll" technique to unroll
itself to the most efficient function after a few tests have been run
the first time.

Okay, my syntax has changed a bit, but see my "improved" function
below. It passes all the jQuery test cases and the additional test
cases from the article on "DHTML Kitchen". It's a little bit weird and
could surely stand to be improved, so go ahead and critique all you
want! I've tested it in IE6, FF2, and Safari3 on Windows. That's all I
have access to at the moment.

var isFunction = (function() {
/*@cc_on
// IE reports some element node methods as objects, but when
converted to string
// they always have the text 'function' and '[native code]' in them
when they
// are actually functions.
// Anything reported as typeof=='function' is always a function
(AFAIK).
return function(o) {
if (typeof o=='function') { return true; }
if (typeof o=='object') {
if (/function/.test(o) && /\[native code\]/.test(o)) {
return true;
}
}
return false;
}
@*/

// For other browsers, we're going to build a function as a string
and only add the
// tests that are actually required by the browser to overcome bugs/
quirks
var func = "";
func += "var o2;\nif(typeof o !='function') { return false; }\n";

// Check for Firefox because it says
document.createElement("object")=='function'
// If the object has a nodeName property (looks like an element
node), then try to create
// a new element of the same type to see if it is typeof=='function'.
If so, we can
// conclude that the original object was an element, not a real
function
if (document.createElement && typeof
document.createElement('object')=='function') {
func += "if (o.nodeName && document.createElement && (o2 =
document.createElement(o.nodeName)) && typeof o2=='function') { return
false; }\n";
}
// Firefox reports RegExp objects as 'function'
if (typeof new RegExp=='function') {
func += "if (typeof o.exec=='function' && /^\\/.*\\/\\w*$/.test(o))
{ return false; }\n";
}

// Safari reports NodeList objects as 'function'
if (document.images && typeof document.images=='function' && /\
[object HTMLCollection\]/.test(document.images) ) {
func += "if (/\[object/.test(o)) { return false; }\n";
}

// The default case - if it says it's a function, trust it
func += "return true;\n";

return Function("o",func);
})();

Matt Kruse
 
V

VK

Okay, my syntax has changed a bit, but see my "improved" function
below. It passes all the jQuery test cases and the additional test
cases from the article on "DHTML Kitchen". It's a little bit weird and
could surely stand to be improved, so go ahead and critique all you
want! I've tested it in IE6, FF2, and Safari3 on Windows. That's all I
have access to at the moment.

Feel free to disregard any of my comments. In the original code my
comments marked as // # (with number sign)
Besides the commented changes I just extra broke some long strings
exclusively for the Usenet post to try to avoid code-smashing wraps.

var isFunction = (function() {

// # conditional compilation needs a condition;
// # also once turned on it is highly suggested
// # to turn it off by the end.
// #
// # JScript pragma commands structure adjusted.

/*@cc_on @*/

/*@if (@_jscript)

// IE reports some element node methods as objects,
// but when converted to string they always have
// the text 'function' and '[native code]' in them
// when they are actually functions.
// Anything reported as typeof=='function' is always
// a function (AFAIK).

return function(o) {
if (typeof o=='function') {
return true;
}

// # if - else if - else, no pending 'if'
// # Not an error but if it's for public
// # attention then let's be accurate ;-)

else if (typeof o=='object') {

// # On IE for DOM elements implicit toString call
// # returns either "function ..." or "[object]"
// # or "unknown" (for some external ActiveX controls):
// # so the 2nd check adds absolutely nothing to the job:
// # /\[native code\]/.test(o) removed
// #
// # At the same time for JScript objects toString
// # method is often overloaded, say to return empty
// # string to prevent source dump.
// # Because the problem affects only DOM elements
// # and because DOM elements are strictly separate
// # from JScript objects in IE then to secure ourselves
// # from overloaded toString and from unnecessary checks:

return Object.prototype.isPrototypeOf(o) ?
typeof o : /^function/.test(o);
}
}

@else @*/

// For other browsers, we're going to build a function
// as a string and only add the tests that are actually
// required by the browser to overcome bugs/quirks.

// # nodeName block further down is optimized to
// # avoid intermediary var assignment: o2 declaration
// # removed.

var func = "if(typeof o !='function') { return false; }\n";

// Check for Firefox because it says
// document.createElement("object")=='function'
// If the object has a nodeName property
// (looks like an element node), then try to create
// a new element of the same type to see if it is typeof=='function'.
// If so, we can conclude that the original object was an element,
// not a real function
// #
// # The bug is exposed for APPLET and OBJECT to be exact.
// #
// # As you check for document.createElement method on
// # method construction then there is no need to re-check
// # it on each method call: nobody will steal it from you :)

if (document.createElement &&
typeof document.createElement('object')=='function') {
func+= "if (o.nodeName && " +
"typeof document.createElement(o.nodeName)=='function')" +
"{ return false; }\n";
}

// Firefox reports RegExp objects as 'function'
if (typeof new RegExp=='function') {
// * If you want to know about RegExp instances
// * then check for RegExp instances! JavaScript
// * maybe not C++ but at the same time not some
// * GBASIC to spit on OOP completely ;-)
func += "if (RegExp.prototype.isPrototypeOf(o))"+
"{ return false; }\n";
}

// Safari reports NodeList objects as 'function'
// # document.images or document.forms are not NodeList
// # but HTMLCollection. NodeList would be from say
// # document.getElementsByTagName(tname). The principal
// # differences are read-only, no update on iteration
// # for HTMLCollection.
// # Is document.getElementsByTagName('IMG') also
// # reported as "function" by Safari?
if (document.images && typeof document.images=='function' &&
/\[object HTMLCollection\]/.test(document.images) ) {
func += "if (/\[object/.test(o)) { return false; }\n";
}

// The default case - if it says it's a function,
// trust it
func += "return true;\n";

return Function("o",func);

// # Switch pragma reader off:
/*@end @*/
})();
 
V

VK

// # Because the problem affects only DOM elements
// # and because DOM elements are strictly separate
// # from JScript objects in IE then to secure ourselves
// # from overloaded toString and from unnecessary checks:

return Object.prototype.isPrototypeOf(o) ?
typeof o : /^function/.test(o);
}
}

Oops... The function always returns true/false, not typeof results:

return Object.prototype.isPrototypeOf(o) ?
(typeof o == 'function') : /^function/.test(o);
 
M

Matt Kruse

// # conditional compilation needs a condition;

No it doesn't. It will run in the JScript engine, and won't elsewhere.
No need for a condition.
// # if - else if - else, no pending 'if'
// # Not an error but if it's for public
// # attention then let's be accurate ;-)
else if (typeof o=='object') {

No need for else if the first block returns early from the function.
// # On IE for DOM elements implicit toString call
// # returns either "function ..." or "[object]"
// # or "unknown" (for some external ActiveX controls):
// # so the 2nd check adds absolutely nothing to the job:
// # /\[native code\]/.test(o) removed

Actually, it is needed. In your version,
isFunction( ["function"] )
would return true.
// # nodeName block further down is optimized to
// # avoid intermediary var assignment: o2 declaration
// # removed.

True, that was leftover from previous code and unnecessary.
// # As you check for document.createElement method on
// # method construction then there is no need to re-check
// # it on each method call: nobody will steal it from you :)

Oops, good catch on that typo.
// * If you want to know about RegExp instances
// * then check for RegExp instances!

I suppose. I've never used isPrototypeOf, so I'd have to make sure
that it's a good choice to use it in this case.
// Safari reports NodeList objects as 'function'
// # document.images or document.forms are not NodeList
// # but HTMLCollection.

Yes. I originally coded it with something that was a NodeList, and
that's when I wrote the comment.
// # Switch pragma reader off:
/*@end @*/})();

Not really necessary.

Thanks for a few useful comments.

Matt Kruse
 
D

dhtmlkitchen

I'm thinking the answer is to avoid having strong reliance on typeof
in checking arguments to functions, and then making further
assumptions based on the return of typeof. This is the typical fake
overloading approach -- switch on arguments[0], et c.

The reason the libraries get themselves in this situation is that
they're using this approach.

It's the fake overloading approach that is the problem. It is a
problem because it assumes things based on the return of typeof.

And so the best approach is to avoid using fake method overloading
with variant types/variant behavior per method. Instead, I think a
better approach is to use explicit methods.

That's my opinion,.
 
R

Richard Cornford

Matt said:
Okay, my syntax has changed a bit, but see my "improved"
function below. It passes all the jQuery test cases and
the additional test cases from the article on "DHTML
Kitchen". It's a little bit weird and could surely stand
to be improved, so go ahead and critique all you want!
I've tested it in IE6, FF2, and Safari3 on Windows.
That's all I have access to at the moment.

var isFunction = (function() {

If there is one thing that is clear from this discussion it is that this
is a very wrong name for this function to have. This function is
attempting to make a discrimination, but that discrimination is not
between objects that are functions and objects that are not functions.
If that was the discrimination that was to be made it would then be
essential to pin down what it was precisely that qualified an object as
a function (and it may be necessary to accept that such a discrimination
was an impossibility if the objects were to include host objects).

In practice JQuery has designed in an issue of discriminating that is
very different form identifying objects that are or are not functions.
To date the precise nature of this requirement have never been stated
here, and without that it designing an appropriate test to make the
discrimination is impractical.
/*@cc_on
// IE reports some element node methods as objects,

Windows IE (but not Mac IE) report element node methods as typeof ==
'object'. In javascript functions/methods are objects.
but when converted to string
// they always have the text 'function' and '[native code]'
in them when they
// are actually functions.
// Anything reported as typeof=='function' is always a function
(AFAIK).
return function(o) {
if (typeof o=='function') { return true; }
if (typeof o=='object') {
if (/function/.test(o) && /\[native code\]/.test(o)) {
return true;
}
}
return false;
}
@*/

// For other browsers, we're going to build a function as
a string and only add the
// tests that are actually required by the browser to
overcome bugs/ quirks

You have not yet identified any bugs here.
var func = "";
func += "var o2;\nif(typeof o !='function') { return false; }\n";

// Check for Firefox because it says
document.createElement("object")=='function'

And as the object in question appears to be callable that is a
reasonable thing for Firefox to be saying.
// If the object has a nodeName property (looks like
an element node), then try to create
// a new element of the same type to see if it is
typeof=='function'. If so, we can
// conclude that the original object was an element,
not a real function

You may be able to conclude that the original object was an element
(which is an object (any object) implementing the Element interface) but
there is no sense is saying that it was "not a real function", because
functions are objects, may implement the Element interface, and your
example of this case certainly looked callable.
if (document.createElement && typeof
document.createElement('object')=='function') {
func += "if (o.nodeName && document.createElement && (o2 =
document.createElement(o.nodeName)) && typeof o2=='function') { return
false; }\n";
}
// Firefox reports RegExp objects as 'function'
if (typeof new RegExp=='function') {
func += "if (typeof o.exec=='function' && /^\\/.*\\/\\w*$/.test(o))
{ return false; }\n";
}

// Safari reports NodeList objects as 'function'

As do (at minimum) Mac IE, Konqueror, Opera versions at least up to 8,
Ice browser and some others. This is in fact so common that you really
have had to have your head buried in the sand for many years not to have
anticipated that this would be an issue.
if (document.images && typeof document.images=='function' && /\
[object HTMLCollection\]/.test(document.images) ) {
^^^^^^^^^^^^^^^^^^^^^
There is nothing that specifies the return value from a nodeList's -
toString - method so this test will be no better than it has been
observed to be. Leaving all the other browsers that have functions as
the object that implements the NodeList interface likely to be fooling
this code.
func += "if (/\[object/.test(o)) { return false; }\n";
}

// The default case - if it says it's a function, trust it
func += "return true;\n";

return Function("o",func);
})();

It cannot possibly be the case that the JQuery functions that need this
discrimination have been written with the expectation that thy can be
passed anything at all (that would be insane). So there must be some
specific set of possible arguments to be tested, and some particular
discrimination to be made between them. It seems to me that it may be
more productive to forget about this 'isFunction' discrimination and
instead set about pinning down what it is that the JQuery function(s)
really needs to know about its arguments, as there may be far simpler
'duck typing' tests that could be applied in context

Richard.
 
V

VK

Fails across windows, yes?

Sorry for delay, I had to be out of town for a while..

One cannot use constructors from one window/frame for object
instantiations in the current window/frame: that leads to one of the
most obscure and random runtime errors in JScript: "Can't execute code
from a freed script". It may work for iframe, mostly fails for
frameset and window. Some maniacs still trying to hack it by using
call wrappers like
var foo = OtherWindowReference.FunctionName();
and in OtherWindow:
FunctionName() {
return new MyObject();
}

"freed script" error still comes sooner or later but even in more
random and obscure way. So yes, such misuse can be met in some
libraries, I saw it myself - but we decided(?) that we don't consider
neither developers devoted to make a non-working code, nor end-users
devoted to crash the program. From this point of view any arguments
about cross-Global OOP should be dismissed; as well as say a
possibility to use constructors and then patch __proto__ property
(where available).
 
V

VK

No it doesn't. It will run in the JScript engine,
and won't elsewhere. No need for a condition.

You are wrong: the minimum required syntax for pragma reader is:
/*@cc_on @ */
/*@if (some_condition)
// some source code
@else @*/
// some source code
/*@end @*/

The code you have originally posted simply leads to the "syntax error"
on IE. Without closing @end pragma it is still syntax error but more
informative: about missing end pragma. Sometimes it is not needed to
build any complex condition and the question only is "IE or not IE".
In such case one normally uses @if (@_jscript) check - it always
returns true for IE. So then really minimalistic conditional
compilation syntax is:

/*@cc_on @*/
/*@if (@_jscript)
// some source code
@else @*/
// some source code
/*@end @*/

One of defaults of the current implementation is that there is no
explicit way to write fall-through branching: so "include this if
(true) but also include the rest of source". As a workaround one
normally uses the above block with empty "else" branch:

/*@cc_on @*/
/*@if (@_jscript)
// some source code
@else @*/
// NOP
/*@end @*/
// the rest of code

Pre-processor has to be turned off as soon as not needed anymore.
Don't forget that it is not a parser on this stage - it is pre-
processor preparing text source code to be sent to parser. Don't make
him to keep going through the whole 10Kb - 600Kb js file in search of
more pragma commands. Inefficient first, dangerous second - in case of
same obfuscated source on the go.
// # if - else if - else, no pending 'if'
// # Not an error but if it's for public
// # attention then let's be accurate ;-)
else if (typeof o=='object') {

No need for else if the first block returns early from the function.
OK
// # On IE for DOM elements implicit toString call
// # returns either "function ..." or "[object]"
// # or "unknown" (for some external ActiveX controls):
// # so the 2nd check adds absolutely nothing to the job:
// # /\[native code\]/.test(o) removed
Actually, it is needed. In your version,
isFunction( ["function"] )
would return true.

On what browser? Again, you seem mixing property values of objects and
toString return value of object itself: they have nothing to do with
each other unless overloaded toString.
I suppose. I've never used isPrototypeOf, so I'd have to make sure
that it's a good choice to use it in this case.

You are not making a startup stage feature sniffing: you are making a
method for using runtime, possibly with hundreds and more calls at a
short period of time. In such case the proportion "more checks / more
speed" has to be shifted to "more speed" especially when the language
provides legal tools for it. IMHO.
Not really necessary.

See the top of my post.
/*@end @*/
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
473,767
Messages
2,569,572
Members
45,045
Latest member
DRCM

Latest Threads

Top