On Tue, Oct 07, 2014 at 02:25:24PM -0700, Jeremy Powers via Digitalmars-d wrote: > On Mon, Oct 6, 2014 at 6:19 PM, Andrei Alexandrescu via Digitalmars-d < > digitalmars-d@puremagic.com> wrote: > > > On 10/6/14, 4:46 PM, Jeremy Powers via Digitalmars-d wrote: > > > >> On Mon, Oct 6, 2014 at 7:50 AM, Andrei Alexandrescu via Digitalmars-d > >> I'm thinking a simple key-value store Variant[string] would > >> accommodate any state needed for differentiating among > >> exception kinds whenever that's necessary.
We've argued about this before. I don't think it's a good solution. Who decides which string keys will map to what types? And how does the catcher know if the thrower is actually using a specific string key to mean that specific thing, and not something else? For example, a catch block receives an Exception with the key/value pair "name" = "abc". How does it know the correct interpretation of this value? A "file not found" error might use "name" as the offending filename, and a database error might use "name" to mean data from a column called "name". How is the catcher supposed to act on this information? The only reasonable way to determine the "true" meaning of a field is if there is an agreement between the thrower and catcher as to what it means. Boiled down to the essentials, what this is saying is, here we have a blob of data, and this data may be divided into chunks with specific names and types, and the thrower and catcher must agree on the meaning of each chunk when presented in a specific combination (e.g., a "file not found" error might contain a field for storing the errno value from the OS). The simplest way to decide if a particular blob has a particular meaning, is to standardize on an ID field that assigns unique identifiers to specific combinations of field names/values with specific meanings. If we look at the essentials of this, it's basically describing what the type system does. So why not just *use* the type system we already have in the first place??! [...] > > I've used "kinds" intentionally there. My basic thesis here is I > > haven't seen any systematic and successful use of exception > > hierarchies in 20 years. In rare scattered cases I've seen a couple > > of multiple "catch"es, and even those could have been helped by the > > use of a more flat handling. You'd think in 20 years some good > > systematic use of the feature would come forward. It's probably time > > to put exception hierarchies in the "emperor's clothes" bin. Sure, but my contention is that Variant[string] is an even worse form of emperor's clothes. You can't do *anything* with it that you can't already do with the existing type system. Not anything meaningful, anyway. The most you can do with it is to print out the key/value pairs, which toString already does today. You need to establish some universal key names whose interpretation everyone agrees on, before your catch block can even begin to do anything meaningful with it. But if so, why not just add these universal fields as members in class Exception? They serve the same purpose, once you strip away the guises of genericity that Variant is supposed to provide -- and do so in a more type-safe manner. As for key names that are non-standard, your catch block basically has no way to act on it, except proceeding by blind assumption (oooh, field "xyz" has type int, so it must mean the line number!). The only way to act on it is if you already established an agreed-on interpretation of field names between the thrower and the catcher. Which, again, is what the type system does. You just subclass Exception, give it the fields that both thrower and catcher agree to, and that use that subclass in the catch block. I don't see any way you can meaningfully act on a generic Variant[string] blob without any established conventions of what each key is supposed to mean. (And again, as soon as you establish an agreed-on convention for these key names, they become equivalent to member fields in an Exception subclass, so you gain absolutely nothing in terms of functionality by replacing the class hierarchy with a poor man's form of runtime-struct.) If you have a counterexample, I'd really like to see it. Believe me, I am skeptical about exception class hierarchies too, but I haven't been able to come up with anything that isn't inferior. [...] > > Oh yah I know the theory. It's beautiful. > > > > > I'm not talking theory (exclusively). From a practical standpoint, if > I ever need information from an exception I need to know what > information I can get. If different exceptions have different > information, how do I tell what I can get? Types fits this as well > as/better than anything I can think of. Exactly. There's nothing Variant[string] gains for us that Exception types don't already give us. A catch block that has no idea what each key in the Variant[string] *means*, cannot meaningfully do anything with the values. I mean, for all you know, it could be a value of a type that the catch block doesn't even know about. How are you supposed to do anything with it? You can't. Except trivial things like printing it out, which is what toString already does anyway. Basically, the catch block cannot do anything with a completely unknown Exception type (or kind, whatever) -- because, by definition, the person who writes the catch block doesn't know anything about that type, so there is no way to make use of any info it may have. There has to be some kind of prior agreement between the thrower and the catcher, for any meaningful processing to take place. This kind of agreement is precisely what the type system was invented to solve -- think of the analogy with calling a function -- you pass the function an opaque blob of binary data, and it really can't do very much with it. The type system is what assigns meaning to individual segments in this blob, i.e., individual function parameters, and that's how the function can make sense of what the caller intends to convey, and how to act on it. In a sense, the catch block is a "remote function" that gets "called" by the throw statement. In order for it to do something useful, the meaning of what the throw statement throws must be agreed upon by the catch block. This is precisely what the type system does. Using Variant[string] gains nothing -- you've basically just reinvented Javascript function calls where parameters have arbitrary type. > >> It's commonly accepted that the usability scope of OOP has > >> gotten significantly narrower since its heydays. However, > >> surprisingly, the larger community hasn't gotten to the point > >> to scrutinize object-oriented error handling, which as far as I > >> can tell has never delivered. > >> > >> > >> Maybe, but what fits better? Errors/Exceptions have an inherent > >> hierarchy, which maps well to a hierarchy of types. When catching > >> an Exception, you want to guarantee you only catch the kinds > >> (types) of things you are looking for, and nothing else. > >> > > > > Yah, it's just that most/virtually all of the time I'm looking for > > all. And nothing else :o). Fallacy: I have never needed X, therefore nobody else needs X either. > Most/virtually all of the time I am looking only for the kind of > exceptions I expect and can handle. If I catch an exception that I > was not expecting, this is a program bug (and may result in undefined > behavior, memory corruption, etc). Catching all is almost _never_ > what I want. The only thing useful about catching all is to print an error message and exit (or restart whatever operation it is you're doing). There's not much else you can do with it, because it's too generic. To act on a specific exception, you need to have narrower exception types, IOW, a subclass of Exception. > I have not found a whole lot of use for deep exception hierarchies, > but some organization of types/kinds of exceptions is needed. At the > very least you need to know if it is the kind of exception you know > how to handle - and without a hierarchy, you need to know every single > specific kind of exception anything you call throws. Which is not > tenable. Exactly. On Tue, Oct 07, 2014 at 02:33:26PM -0700, Jeremy Powers via Digitalmars-d wrote: [...] > As mentioned, I'm not a defender of hierarchies per se - but I've not > seen any alternate way to accomplish what they give. I need to know > that I am catching exceptions that I can handle, and not catching > exceptions I can't/won't handle. Different components and layers of > code have different ideas of what can and should be handled. > > Without particular exception types, how can I know that I am only > catching what is appropriate, and not catching and swallowing other > problems? Exactly. Having said all that, though, an idea occurred to me. While it's certainly true that the catch block must catch a *specific* type of exception -- otherwise there's no meaningful way to act upon it -- I haven't seen any strong evidence that a *class hierarchy* is required. Having been a skeptic of the OO craze where everything and everyone is shoehorned into the OO mode of thinking, misfits be damned, and having experienced the true power of metaprogramming in D, I wonder if a more flexible approach would be to use a templated, duck-typing kind of approach to exceptions instead of a class hierarchy. Basically, this boils down to the thought that the relationship between the throw statement and the corresponding catch block is in many ways analogous to a function call: the thrower (caller) has some pieces of info to pass on to the catcher (callee), and the type system allows the two to communicate and agree on the interpretation of this info. The current exception class hierarchy approach is akin to requiring all catch blocks to have signature function(Exception e). But what if we allow catch blocks to be "templated"? We could then introduce signature constraints to catch blocks, that determine generically whether some unknown incoming exception type matches certain required characteristics (in the same way range algorithms accept any type that satisfy the range API), and then the catch block would be able to operate on that exception without actually being tied down to a concrete exception type. For classification purposes, of course, we still require exceptions to subclass Exception. But a catch block could potentially accept multiple unrelated Exception subclasses via ducktyping, e.g.: class Exception : Throwable { ... } class JSONParseError : Exception { string filename; size_t line; JSONParser parser; } // N.B. there is no common base class between ScriptRunnerError // and JSONParseError. class ScriptRunnerError : Exception { string filename; size_t line; Interpreter interp; } ... void main() { try { doStuff(); } catch (E : Exception)(E e) if (is(typeof(e.filename) : string) && is(typeof(e.line) : size_t)) { // can use .filename and .line here. } } This way, things don't have to be in a hierarchy, yet the catch block can meaningfully process any Exception subclass that has the requisite attributes. (The caveat, of course, being that the current language may not be powerful enough to support such a feature, since implementing the catch block sig constraints will require runtime ducktyping.) T -- You have to expect the unexpected. -- RL