Just before I commence a discussion on the Lucee Language Forum, I thought I'd do a code sample and ask what people think.
The question is stated in the subject line: "what manner of object should an Exception be?"
Here's some code:
// typeValidation
samples = [
{type="struct", example={}},
{type="cfmlObject", example=new Sample()},
{type="javaObject", example=createObject("java", "me.adamcameron.miscellany.Sample")},
{type="javaException", example=createObject("java", "java.lang.Exception").init("Message")}
];
try {
1/0;
}catch(any e){
samples.append({type="cfmlException", example=e});
}
tests = ["isStruct", "isObject"];
samples.each(function(sample){
writeOutput("<hr><h2>#sample.type#</h2>");
tests.each(function(test){
var result = (evaluate("#test#(sample.example)")) ? true : false;
writeOutput("#test#(): #result#<br>");
});
});
This loops over some sample objects, and for each of those tests whether the object passes a test. I'm testing for
isStruct()
and isObject()
. Note I threw a cheeky evaluate()
in there ;-)On ColdFusion, the output is this:
struct
isStruct(): trueisObject(): false
cfmlObject
isStruct(): trueisObject(): true
javaObject
isStruct(): falseisObject(): true
javaException
isStruct(): falseisObject(): true
cfmlException
isStruct(): falseisObject(): true
Notice how a CFML Exception is an object, not a struct. Same as a Java Exception (and a Java object, and a CFML object). That makes sense.
On Lucee, the output is the same except this bit:
cfmlException
isStruct(): trueisObject(): false
This is the point of contention (as per LDEV-323). Micha seems to think an exception should be a struct for some reason. I'm not sure why... and it would simply never occur to me that an exception might be anything other than an object. Exceptions are objects.
Now I'd be completely fine if as well as being an object then the Lucee implementation of Exceptions also implemented some sort of interface (say and interface "StructCompatible" or something) that struct functions except for their "struct" argument. Indeed ColdFusion seems to at least partly do this, as this code works:
structKeyExists(samples[5].example, "type") // that's the CFML exception from above
So ColdFusion accepts an Exception object as an argument for struct functions.
However this doesn't work on ColdFusion:
samples[5].example.keyExists("type")
It errors with:
The keyExists method was not found. | |
Either there are no methods with the specified method name and argument types or the keyExists method is overloaded with argument types that ColdFusion cannot decipher reliably. ColdFusion found 0 methods that match the provided arguments. If this is a Java object and you verified that the method exists, use the javacast function to reduce ambiguity. |
Note if I run that same code on a native CFML struct, it's fine:
samples[1].example.keyExists("type")
(
false
in this case, as the struct I'm using is empty)
If Adobe had done a complete job here, then a ColdFusion Exception would also implement the methods to make it "StructCompatible". It makes no sense to be able to call the headless function OK, but not the same functionality when called as a method.
Lucee is even weirder. Remember it claims that a CFML exception is actually a struct. Is it now? Check this:
samples[5].example.keyExists("type") // that's the CFML exception from above
Lucee 5.0.0.43 Error (expression) | |
Message | No matching Method/Function for lucee.runtime.exp.NativeException.keyExists(string) found |
Stacktrace | The Error Occurred in C:\blogExamples\datatypes\objects\typeValidation.cfm: line 27 25: |
So... erm... how come one cannot call a struct method on it?
I raised two issues with Lucee:
- This should be true: isObject(anExceptionObject) (LDEV-323)
- If structKeyExists() works on an Exception, so should .keyExists() (LDEV-322)
Micha's fixed the latter one (but only in Lucee 4.5 it seems), which is good (-ish).
However the conversation went completely off the rails on the other ticket, with Micha saying some rather odd things. When I suggested an Exception should be an object, and it should implement the StructCompatible interface, he said this:
It just means that your exception objects also need to implement the same interface as structs.(the bold bit is him quoting me inline, for context; the rest is his reaction to same)
what makes it a struct and make IsObject return false, my point. implementing the struct interface makes an object a struct, implement the array interface a array, ...
"implementing the struct interface makes an object a struct, implement the array interface a array". I'm sorry, but what? Yer joking, right? That's demonstrating a scary lack of understanding of OO, in my view.
For clarification (for Micha on the ticket, and here):
I meant to comment on this:
> implementing the struct interface makes an object a struct
Well, no, it doesn't. That is a really naive understanding of OO, and I can only presume it's lazy wording (or poor translation into English) on your part.
If there's a classC
, and its entire behaviour is defined by interfaceI
, it can be said thatC
is anI
, sure. But it's still also aC
even though it does not implement any behaviour beyond that which is defined inI
.
So if there's aStruct
implementation class and aStructAPI
interface, aStruct
is aStruct
and it's also aStructAPI
. There would quite possibly be no type references in the Lucee codebase expecting aStruct
, but instead aStructAPI
is expected (NB: I'm only using the "API" suffix here to make distinct the difference between the implementation and the interface).
Now if there's then a classA
and it implements interfaceI
, then it can also be said that anA
is anI
, but it cannot be said thatA
is aC
simply becauseA
implements the same behaviour asC
. For one thing, it also implements all its own behaviour as well.
So if there's a classException
(behaviour defined byExceptionAPI
) it might also implementStructAPI
so that it can share behaviour (method names) with aStruct
, and also be used where aStructAPI
is expected (as function arguments or return types). This, however, does not make anException
aStruct
. It's still anException
. It can also be used wherever anExceptionAPI
is expected, and it still has itsExceptionAPI
behaviour. If some method was expecting aStruct
(which would be bad implementation), then anException
would not work.
If you're saying you're not re-implementingException
objects as being actualStruct
instances, then you've done the wrong thing. All theException
class should be doing is implementing theStructAPI
interface. But still be a discreteException
class.
So they should pass bothisObject()
,isStruct()
tests. And if there was anisException()
test, they should pass that as well.
The conversation manages to go even further off the rails after that, but it's inconsequential.
Micha's suggested the conversation needs to switch to the Language Forum for some reason, so I'll do that.
But because Micha & I seem to have a different understanding of how interfaces ought to work and the ramifications thereof, I thought I'd get you lot to sanity check things before I go ahead.
So... about my sanity then..?
Cheers.
--
Adam