Now that I think I finally found an actual way to re-implement deep_twin and is_deep_equal with a pure Eiffel, reentrant, thread safe design (beware that I still have to test it extensively before saying it's good even to be labelled "alpha-quality") I must answer to those issues, since most of them don't hold anymore.
In particular, although I like the assertion stuff in Eiffel, I think the language has a number of "inelegant" aspects. For example:
- exception handlers only at the top level of a routine,
with the only way to "handle" an exception being by retrying
the whole routine.
As far as I remember this have been a deliberate design choice of Meyer. The rationale behind this is that if a feature, either a command or a query is longer than a 20-30 lines it becomes far more difficoult to understand; it becomes easier to understand for the reader if it is broken in several independant pieces. Therefore the need for intra-feature exceptions fades away: when you need to handle an exception in a specific section of a feature this is the distinguished mark that that piece could be made an independant feature. If you don't want to expose it to your clients just hide it private behind a "feature {} -- Implementation of foo feature".
- No way to return from a routine in the middle.
This makes it a pain in the neck to search through
a list for something in a loop, and then return immediately
when you find what you want. (I have never found the
addition of extra boolean control variable a help to
the understanding of an algorithm.)
This is actually something I found myself longing to sometimes.
- Namespace control handled by a separate sublanguage, and no real higher level concept of "module" or "subsystem."
- An obscure notation like "!!" being used for an important
and frequent operation (construction).
This was a good point. In fact the "!!" notation have been (somehow) deprecated; while it has never been phased out by the language itself I have never read it in code writter in this century. Nowadays all Eiffel programmers worth their salt always use one of the following syntaxes:
- "foo: LINKED_LIST[STRING] .... create foo" used when the created object be an actual instance as declared, using "default_create" feature to initialize it;
- "foo: SET[STRING] .... create foo.make_with_capacity(120)", the most widespread usage where you tell the compiler which creation feature shall be used to initialize the newly created object
- "foo: COLLECTION[STRING] .... create {TWO_WAY_LINKED_STRING} foo" used when you want to create an object of a subtype of the declared type, either because the declared type is deferred (a.k.a. virtual for you people coming from C++/Java world) or because you want to use a subclass that better fits the task.
- manifest notation like "foo := {TWO_WAY_LINKED_LIST[STRING] << "Some", "strings", "in", "a collection">> }" or "{RING_ARRAY[CHARACTER] 1, << "These", "strings", "live in", "a collection", "which is actually a RING_ARRAY">> }"; you may read further examples in MANIFEST_NOTATION tutorial
- in-place creation, in the middle of the arguments of a feature "produce_and_ship_with(12, "Chocolate cakes", create {CAR}.with_plate_and_label(clients_car_plate, "The car of client "|client_name)"; by the way this line show the usage of one of the three concatenation operators we introduced in strings. I should really write a blog entry about these... (they are infix "+", infix "|" and infix "&", see ABSTRACT_STRING for a brief description.
- No way to conveniently "use" another abstraction without
inheriting from it.
This is exactly what both Eiffel standards (ECMA and GNU/Smart) implemented in this decade: non-conforming inheritance; now when you need to use another abstraction without being a type conforming to it you "insert" it.
- No strong distinctions between integer types used for array indexing.
I think this is a "non issue" at all, since array access does not have any special status at all in Eiffel; actually you may just define your arrays to accept different types on indexes. I suspect that this
- Using the same operator ":=" for both (aliasing) pointer assignment, and for value assignment, depending on whether the type is "expanded." (Simula's solution was far preferable, IMHO).
This were actually confusing some years ago since you may decide in the definition of each feature whenever an argument shall be expanded or not. Now in GNU/SmartEiffel this is notnot confusing in my humble opinion in since the difference between assignment by reference or by value (expanded values) is estabilished class per class; when you need to have a reference to an expanded class you just use REFERENCE[FOO] with FOO being an expanded class. By the way you may read the definition ofREFERENCE discovering that it is a simple, plain and normal generic class that does not require any special support from the compiler at all. I think that deciding once and forever if a class is either a reference or an expanded value makes code far easier to understand for the reader, easier for the designer to conceive and easier for the compiler to parse and optimize.
And most critically:
- No separate interface for an abstraction. You can view a interface by running a tool, but this misses completely the importance of having a physical module that represents the interface, and acts as a contract between the specifier or user of an abstraction and its implementor. In Eiffel, one might not even be truly aware when one is changing the interface to an abstraction, because there is no particular physical separation between interface and implementation.
Again this is a deliberate and willingful consequence of an explicit design choice; it has been extensively explained by Meyer in Object Oriented Software Construction; this would be a nice topic for a foreseeable blog entry as it's a sin not to explain it to people on the net; I wish Meyer published OOSC directly on the net as I'm sure that the profits for the advertizing that he would put will more than compensate his lost revenues.
No comments:
Post a Comment