Difference between revisions of "Talk:Objectless Calls"
(Added comment.) |
|||
(One intermediate revision by one other user not shown) | |||
Line 159: | Line 159: | ||
---- | ---- | ||
− | + | --[[User:Clemahieu|Clemahieu]] | |
I think a generic definition of the problem case, where people like to use static functions, is a situation like one I ran in to. I wanted a way to convert an integer to an array of bytes to send over a network.<br/><br/>Most people would say, create a static function int_to_bytes(i: INTEGER): ARRAY[NATURAL_8]. The English phrasing of this: you're applying a function INTEGER -> ARRAY[NATURAL_8] to your variable `i'. This could be put in a new class INT_UTILITY and statically accessed or inherited from.<br/><br/> A more correct and object oriented way would be to edit the INTEGER class and add this feature in. The English phrasing of this: you are asking this instance of INTEGER to convert itself to ARRAY[NATURAL_8].<br/><br/>Two issues can arise with this, if this is a pre-existing library you need to edit a class that is supposed to be consumed unmodified. The other issue is the number of permutation of this in infinite. There are an infinite number of ways to manipulate INTEGER and most are problem domain specific, so if in fact you could add this to the INTEGER class, there would be an unlimited number of additions to this class which is not feasible.<br/><br/>Evidently a solution to this was brought up at ECMA before, as I discovered when I posted http://dev.eiffel.com/Implicit_class What we're looking to do is add features to an existing class without modifying the class. A statically-checkable downcast would allow you to use a new feature on an existing class in an object oriented way new.f instead of a functional way new(f).<br/><br/> I like the solution of an implicit class, or whatever it could be called, for a couple other reasons. It cleans up the renaming definition of multiple generic constraints and would allow multiple actual generic parameters like LIST[{COMPARABLE, HASHABLE}]. Wasn't it Bertrand that said the most elegant solutions solve multiple problems?<br/><br/>As for the original rationale of a performance issue. If you allow redefinition with the same power Eiffel allows in other places, we'd run in to issues of dynamic calls.<br/><br/>We English speakers also have an issue of differentiating between one and anything more than one. We want *the* number PI, not an instance of REAL that is close to 3.14159265. We want to #define this so we are sure that *the* version of it is used everywhere. This is a misnomer because ever though we call it static and think there is only one version, it's still one version within the domain of the executing process. If we start another instance of the program, it's no longer the only copy. Information hiding and creating abstract data types is the primary purpose of moving to OO languages. Attaching semantics to the class as opposed to an instance of a class, an object, makes it one instance of something accessible to everything instead of operating on a specific instance of something.<br/><br/>The issue of the singleton object like PI is actually a compiler optimization issue. We could optimize all (create {NATURAL}.make(3.141592)) to be one object, created at compile time and put in to the image of the executable, but our compiler writers are too busy writing implementation of new language specifications to do this! We should be able to write: | I think a generic definition of the problem case, where people like to use static functions, is a situation like one I ran in to. I wanted a way to convert an integer to an array of bytes to send over a network.<br/><br/>Most people would say, create a static function int_to_bytes(i: INTEGER): ARRAY[NATURAL_8]. The English phrasing of this: you're applying a function INTEGER -> ARRAY[NATURAL_8] to your variable `i'. This could be put in a new class INT_UTILITY and statically accessed or inherited from.<br/><br/> A more correct and object oriented way would be to edit the INTEGER class and add this feature in. The English phrasing of this: you are asking this instance of INTEGER to convert itself to ARRAY[NATURAL_8].<br/><br/>Two issues can arise with this, if this is a pre-existing library you need to edit a class that is supposed to be consumed unmodified. The other issue is the number of permutation of this in infinite. There are an infinite number of ways to manipulate INTEGER and most are problem domain specific, so if in fact you could add this to the INTEGER class, there would be an unlimited number of additions to this class which is not feasible.<br/><br/>Evidently a solution to this was brought up at ECMA before, as I discovered when I posted http://dev.eiffel.com/Implicit_class What we're looking to do is add features to an existing class without modifying the class. A statically-checkable downcast would allow you to use a new feature on an existing class in an object oriented way new.f instead of a functional way new(f).<br/><br/> I like the solution of an implicit class, or whatever it could be called, for a couple other reasons. It cleans up the renaming definition of multiple generic constraints and would allow multiple actual generic parameters like LIST[{COMPARABLE, HASHABLE}]. Wasn't it Bertrand that said the most elegant solutions solve multiple problems?<br/><br/>As for the original rationale of a performance issue. If you allow redefinition with the same power Eiffel allows in other places, we'd run in to issues of dynamic calls.<br/><br/>We English speakers also have an issue of differentiating between one and anything more than one. We want *the* number PI, not an instance of REAL that is close to 3.14159265. We want to #define this so we are sure that *the* version of it is used everywhere. This is a misnomer because ever though we call it static and think there is only one version, it's still one version within the domain of the executing process. If we start another instance of the program, it's no longer the only copy. Information hiding and creating abstract data types is the primary purpose of moving to OO languages. Attaching semantics to the class as opposed to an instance of a class, an object, makes it one instance of something accessible to everything instead of operating on a specific instance of something.<br/><br/>The issue of the singleton object like PI is actually a compiler optimization issue. We could optimize all (create {NATURAL}.make(3.141592)) to be one object, created at compile time and put in to the image of the executable, but our compiler writers are too busy writing implementation of new language specifications to do this! We should be able to write: | ||
<e> | <e> | ||
Line 183: | Line 183: | ||
</e> | </e> | ||
This could be optimized at compile time instead of making a manifest array construct in the language definition, but I digress.<br/><br/>Creating new language features to solve specific optimization problems isn't always a good idea, that's what caused the C preprocessor. I see a need for adding features to a class without modifying the class and I see it should be done differently than static methods. | This could be optimized at compile time instead of making a manifest array construct in the language definition, but I digress.<br/><br/>Creating new language features to solve specific optimization problems isn't always a good idea, that's what caused the C preprocessor. I see a need for adding features to a class without modifying the class and I see it should be done differently than static methods. | ||
+ | |||
+ | :--[[User:Peter gummer|Peter gummer]] 00:34, 19 March 2008 (PDT) Paul mentions the word "optimize" twice in the article: it's only a potential bonus of his proposal, not the principal motivation. And his proposal isn't for "static methods" (although it does provide a mechanism for them: a frozen objectless routine would be equivalent to a "static method", I suppose). Your "implicit class" idea is interesting, but it's not clear to me how it addresses the concerns that Paul raised. | ||
+ | |||
+ | :-----[[User:Paulb|paulb]] 15:58, 1 October 2008 (PDT) | ||
+ | Actually any call made using the Eiffel static calling convention would be performed statically instead of dynamic dispatch. In that case objectless calls need not be marked <e>frozen</e> in order to benefit from performance gains. And, although performance is not the generalized issue here, I would like objectless calls to be implemented for performance reasons also. There is a dramatic increase in performance when using static calls as apposed to dynamic dispatch. |
Latest revision as of 13:58, 1 October 2008
Why couldn't the client needing to validate parameters inherit{NONE} from A_VALIDATOR instead of creating an instance?
Also your validation feature doesn't have a postcondition so it's really an incomplete contract. If your validation feature is supposed to ensure something, it needs to state that, otherwise you're just saying, in order to create this object it needs to satisfy this routine which can be overidden and since there's no postcondition, it could be overridden to mean anything including nothing and that's a bug.
Non-polymorphic inheritance shouldn't have a performance hit unless it's not being optimized, by definition they're monomorphic calls so they can be static and inlined.
--Clemahieu 14:43, 14 March 2008 (PDT)
- Paulb 08:40, 18 March 2008 (PDT) Yes, the client class could inherit A_VALIDATOR but I used the model to indicate a latter point.
- In regards to the postconditions, this we me trying to be quick. We are polishing our release off and we are all busy. I just wanted to get something going in the domain of objectless calls.
class A_CLIENT inherit{NONE} A_VALIDATOR feature process (a_value: ?VALUE) local l_a: A do if is_valid_value (a_value) then create l_a.make (a_value) ... end end
--Peter gummer 04:42, 15 March 2008 (PDT) The redefinition of is_valid_value
is strengthening the precondition. This is contrary to Design by Contract. I think you have a valid point, Paul, but you need to come up with a decent example!
- Paulb 08:50, 18 March 2008 (PDT) Yes, sorry, as I mention above, it was a quick implementation because of the release. It could have just as easily weakened.
--Peter gummer 05:19, 15 March 2008 (PDT) I don't like your preferred placement of the objectless keyword, Paul, in front of the feature name. I think I would prefer it in the place of the do
; this is an alternative that you did not propose:
feature -- Access f: STRING_8 objectless end end
A problem with this is that it precludes the possibility of objectless once functions or objectless externals.
I can think of another possibility: in place of feature
. This would be out of step with any other Eiffel keyword; but considering that your whole proposal is out of step with Eiffel's insistence that all calls be targeted to an object, maybe it's appropriate!
objectless -- Access f: STRING_8 do end end
I think I like this. (In fact, why isn't frozen
like this?)
- --Ericb 14:01, 16 March 2008 (PDT): Because one can write:
copy, frozen standard_copy (other: like Current)
- --Peter gummer 17:54, 16 March 2008 (PDT) Ha, good point, Eric! That is useful. So Paul's preferred notation would be advantageous only if someone can see any useful reason to have two synonyms for a routine, one object-oriented and the other class-oriented. Sounds like a very bad idea to me, because
Current
would be meaningless in an objectless routine (unless Eiffel specified that it be a reference to the enclosing class, as Delphi and Smalltalk do for self in class methods; but Eiffel does not have class reference variables, so this won't work in Eiffel; and it would be a pretty stupid idea anyway, for the meaning ofCurrent
to change like that within the same routine). Methinks this would be a defect, then, in Paul's preferred notation! This leaves two possible places for the proposed objectless keyword: to replace or qualify eitherdo
orfeature
.
- --Peter gummer 17:54, 16 March 2008 (PDT) Ha, good point, Eric! That is useful. So Paul's preferred notation would be advantageous only if someone can see any useful reason to have two synonyms for a routine, one object-oriented and the other class-oriented. Sounds like a very bad idea to me, because
- Paulb 08:50, 18 March 2008 (PDT) What about if I wanted an objectless once, deferred and even though it is redundant because it's implicit, an external?
- --Peter gummer 16:38, 18 March 2008 (PDT) Yes, I suppose objectless once would be useful; and people would definitely want objectless external; and although objectless deferred may be dangerous, as mentioned below, the syntax should not preclude its possibility in the future. I think that your objectless do syntax proposal may be the best, although we would might format it with objectless on its own line (like
obsolete
) or even on the same line as the feature names (likeassign
). Maybe it would fit into the grammar somewhere nearobsolete
, before the header comment. Then it would probably work for objectless attributes too.
- --Peter gummer 16:38, 18 March 2008 (PDT) Yes, I suppose objectless once would be useful; and people would definitely want objectless external; and although objectless deferred may be dangerous, as mentioned below, the syntax should not preclude its possibility in the future. I think that your objectless do syntax proposal may be the best, although we would might format it with objectless on its own line (like
--Peter gummer 05:33, 15 March 2008 (PDT) Paul, you wrote, "Unlike other languages, it should be possible to redefine any objectless routine." Uh, which other languages? Well ok, I know exactly which languages you are thinking of: C#, etc. I just want to point out that there are other languages out there that do allow redefinition of objectless routines. The obvious one is the big grandma of OO languages, Smalltalk, where polymorphic class methods are common (or so I believe, never having programmed in Smalltalk myself). Another is Delphi, which I programmed in for about 6 years, and polymorphic class methods are the biggest thing that I miss from the Delphi language. They are incredibly powerful when used in combination with class reference variables (which is something else that Eiffel lacks, although the new TYPE
class and related mechanisms may be a worthy substitute: I'm not sure yet.)
- Paulb 08:50, 18 March 2008 (PDT) Yes things like C#, as I said, like other languages, not all languages.
--Ericb 14:14, 16 March 2008 (PDT): Paul, you said that this was overlooked when defining the ECMA standard. I attended the ECMA meetings and I don't share your opinion. The fact that it is not in the current version of the ECMA standard does not mean that it has been overlooked. That being said, I'm glad you wrote this wiki page so that the topic can be brought back again at ECMA. However there is something that is desperately missing in your proposal: where are the validity rules? There is only one which states that we can redefine an objectless routine (BTW, why not simply call them static routines?) to a non-objectless routine. Personally I think that it should be the reverse, but it's hard to be sure since you don't put down the other validity routines. For example, what should be the conditions that a routine should satisfy in order to be valid to declare it as objectless? My first impression is that it should not use 'Current' nor call non-objectless features, to the very least.
- Paulb 08:50, 18 March 2008 (PDT) I was going based on the reaction here at Eiffel Software. I was given no indication anything of sorts was raised in ECMA and I'm not a member of the committee.
- I'm just trying to get something out there. Granted there are not validity rules so it's not a full proposal. I really do not have the time but I've been wanting to mention objectless calls for over a year, so I took an hour out and wrote the wiki page. I've talked this over with Manu an the validity rules you specified are those that we discussed.
I'm not on the ECMA committee, but I've always seen static functions as poor design. I still question whether the problems stated are valid. -There is no performance hit from creating A_VALIDATOR because you don't need to create an instance, you can use non-conforming inheritance. -The example strengthens a precondition so any pattern in existing code following type B in this example is questionable. -The "overuse of inheritance" is shown as a problem, I see it as code reuse. I hope the suggestion isn't to make global static functions, more than likely it's suggested to be referenced by a {CLASS}.function in which case you're just using composition rather than inheritance so all inheritance just moves to composition. There shouldn't be a performance hit of dynamic dispatch when using non-conforming inheritance because they're non-polymorphic calls. -There is no double object creation, use non-conforming inheritance, and there should be no dynamic calls to monomorphic non-conforming inheritance features.
- Please tell me if I'm missing why these functions need to by dynamic in non-conforming inheritance!****
Static functions that are part of a class basically are there for namespace reasons since they don't represent operations on an instance of the class; The class name because the namespace of the function, essentially. They don't help create an ADT which is the entire purpose of a pure OO language. --Clemahieu 16:44, 16 March 2008 (PDT)
--Peter gummer 17:54, 16 March 2008 (PDT) To answer Eric's question, "Why not simply call them static routines?": because they are not static routines!
Static routines in C++, C#, Java, etc. cannot be redefined, as I discovered to my dismay when I moved from Delphi to C#. Static routines in those languages are nothing more than class-scoped global routines. Yechh! Evil! What Paul is proposing -- and what I found so useful in Delphi -- are known as class methods in languages that are influenced by Smalltalk. They can be redefined. When assigned to a class reference variable in those languages, they become polymorphic. This is extremely powerful. One thing they were great for in Delphi is in object factories: it took me a long time to figure out why the GoF Design Patterns book was describing in such a long-winded way how to do class factories, until finally I understood that I had been doing them for a long time in Delphi, but with only a line or two of code thanks to the combination of class methods, class references and polymorphic constructors. (Unlike the brain-dead constructors in C++, C# and Java, Delphi constructors can be redefined, just like Eiffel creation routines.)
This is anything but "static"!
I don't know whether objectless routines would fit well within Eiffel, but I would certainly be pleased to see them. It would simplify a lot of code: whole classes would disappear, and ten lines would collapse down to one or two in many cases, especially if the TYPE
class turns out to be as useful as class references are in Smalltalk, etc. Bertrand Meyer has written in the past about the dangers of backsliding into procedural code, so maybe there is too much resistance in the Eiffel community. On the other hand, the unwieldiness of the current approach of inheriting SHARED_*
classes is probably a deterrent to many prospective Eiffel programmers.
I would recommend studying patterns of usage of class methods in Smalltalk. A bit of googling I've just done also suggests that Ruby might also be worth looking at. But most instructive, perhaps, would be Delphi, which is syntactically closer to Eiffel and is type-safe. (Although Delphi is not, in general, as type-safe as Eiffel, class methods are done in a type-safe manner).
--Ericb 00:59, 17 March 2008 (PDT): In Eiffel, when a feature cannot be redefined, we use frozen, not static. In Eiffel, static is used in opposition to dynamic. For example, dynamic binding vs. static binding. In case of class methods (hmmm, class features I should say!), unless I miss something we would have static binding. I agree that this does not convey the notion of call with no target object, but objectless looks very ugly to me as a keyword.
I'm still waiting for the validity rules to be stated. Here is another example of such rule: an objectless feature cannot be deferred. The same rule applies to frozen features.
--Peter gummer 19:08, 17 March 2008 (PDT) I would want these objectless features to be dynamically bound, Eric. Unless I misread him, I'm sure that's what Paul is asking for too. This would therefore be valid:
feature frozen i_am_statically_bound objectless end i_must_be_defined_in_effected_descendants objectless deferred end end
Except that I don't like that double-barrelled objectless deferred
syntax! Anyway, whatever the syntax, if I remember rightly Delphi allows "abstract class methods", and I see no reason to forbid deferred objectless routines.
Paul's proposed objectless keyword looks ugly to me too, Eric. But I do like the fact that it is explicit about what it is, and I also like that it probably won't conflict with identifiers in existing software. Why does Eiffel always have to invent its own idiosyncratic terminology? Why not just use the keyword that some other languages are already using for this concept: class
? It's brief, accurate, won't conflict with identifiers in existing code, and will be instantly understood by thousands of Smalltalk and Delphi programmers. Let's see how it would look:
feature f: STRING class end end
Hmmm, looks ok to me. Another possible keyword would be singleton, which apparently is Ruby's keyword for its closest equivalent to Smalltalk class methods.
Another issue: why just routines? Why not deferred objectless attributes? Smalltalk has them, and I think Delphi has them too these days.
- --Ericb 02:16, 18 March 2008 (PDT): My understanding of objectless features is that for
{A}.f
to be valid,f
has to be declared as objectless in class A. What would be the semantics of such expression iff
is deferred? When I say that it is statically bound, I mean that the type{A}
is a static type, known at compilation time. Therefore the compiler knows which version off
to call. Of course callinga.f
wherea
can be attached to an object of a descendant type of A will still be dynamically bound.
- --Peter gummer 14:45, 18 March 2008 (PDT)
- I imagine that, if
f
is deferred in class A, then{A}.f
would be an invalid call. Because{A}.f
is statically bound, the compiler can report this error. That's the trivial case.
- The case of calling
a.f
wherea
is attached to an object descending from A is dynamically bound. Becausea
cannot, by Eiffel's existing validity rules, be attached to a deferred type, this case is type-safe and the semantics are clear.
- The case of calling
- What I'm most interested in, however, is in being allowed to call
c.f
, wherec
is attached to an object conforming toTYPE [A]
. The question arises, then, of what would happen ifc
were attached to an object of typeTYPE [A]
, wheref
is deferred in class A? The answer isn't nice: callingc.f
would produce a run-time error. Ok, I bet I'm scaring people now: I'm poking another little hole into the Eiffel type system which you guys have been working so hard for a decade or more to construct on more solid foundations. But having seen the power of this facility in Delphi, I'd love to have it in Eiffel.
- What I'm most interested in, however, is in being allowed to call
- Maybe the best thing to do would be forbid deferred objectless features. This would avoid the need for the compiler to detect the trivial validity error in
{A}.f
; it would prevent the nasty type hole inc.f
; and it would save us all the effort of trying to figure out what might be the best syntax for declaring a feature objectless deferred. At some future date, someone might discover that objectless deferred features can be added without jeopardising the integrity of the type system; in the meantime, I believe it's best to forbid them.
- Maybe the best thing to do would be forbid deferred objectless features. This would avoid the need for the compiler to detect the trivial validity error in
You mean global variables? --Clemahieu 23:12, 17 March 2008 (PDT)
- --Peter gummer 15:24, 18 March 2008 (PDT) I wouldn't want deferred objectless attributes, because of the possible type hole that I mentioned above. But why not objectless attributes? These are not global variables, although they would be very close to them when called statically, as in
{A}.f
. Even then they differ from global variables in two respects: they are scoped within class A (like static variables in C#, Java, etc.); and they are read-only except within descendants of class A (thanks to Eiffel's normal rules of encapsulation). When called dynamically, as ina.f
, they acquire a new power due to Eiffel's redefinition mechanisms.
--Clemahieu
I think a generic definition of the problem case, where people like to use static functions, is a situation like one I ran in to. I wanted a way to convert an integer to an array of bytes to send over a network.
Most people would say, create a static function int_to_bytes(i: INTEGER): ARRAY[NATURAL_8]. The English phrasing of this: you're applying a function INTEGER -> ARRAY[NATURAL_8] to your variable `i'. This could be put in a new class INT_UTILITY and statically accessed or inherited from.
A more correct and object oriented way would be to edit the INTEGER class and add this feature in. The English phrasing of this: you are asking this instance of INTEGER to convert itself to ARRAY[NATURAL_8].
Two issues can arise with this, if this is a pre-existing library you need to edit a class that is supposed to be consumed unmodified. The other issue is the number of permutation of this in infinite. There are an infinite number of ways to manipulate INTEGER and most are problem domain specific, so if in fact you could add this to the INTEGER class, there would be an unlimited number of additions to this class which is not feasible.
Evidently a solution to this was brought up at ECMA before, as I discovered when I posted http://dev.eiffel.com/Implicit_class What we're looking to do is add features to an existing class without modifying the class. A statically-checkable downcast would allow you to use a new feature on an existing class in an object oriented way new.f instead of a functional way new(f).
I like the solution of an implicit class, or whatever it could be called, for a couple other reasons. It cleans up the renaming definition of multiple generic constraints and would allow multiple actual generic parameters like LIST[{COMPARABLE, HASHABLE}]. Wasn't it Bertrand that said the most elegant solutions solve multiple problems?
As for the original rationale of a performance issue. If you allow redefinition with the same power Eiffel allows in other places, we'd run in to issues of dynamic calls.
We English speakers also have an issue of differentiating between one and anything more than one. We want *the* number PI, not an instance of REAL that is close to 3.14159265. We want to #define this so we are sure that *the* version of it is used everywhere. This is a misnomer because ever though we call it static and think there is only one version, it's still one version within the domain of the executing process. If we start another instance of the program, it's no longer the only copy. Information hiding and creating abstract data types is the primary purpose of moving to OO languages. Attaching semantics to the class as opposed to an instance of a class, an object, makes it one instance of something accessible to everything instead of operating on a specific instance of something.
The issue of the singleton object like PI is actually a compiler optimization issue. We could optimize all (create {NATURAL}.make(3.141592)) to be one object, created at compile time and put in to the image of the executable, but our compiler writers are too busy writing implementation of new language specifications to do this! We should be able to write:
class PI inherit REAL create default_create feature default_create is do make(3.14159265) endThe compiler should be able to see that only one version of this is ever creatable, create it at compile time and put it in to the executable image as an *optimization*. This optimization could be applied to any object that's created from basic types in the language, integers, strings. In fact arrays could have been done this way instead of manifest arrays.
local list: ARRAY[INTEGER] do create list.make(1, 2) list[1] := 8 list[2] := 73 end
This could be optimized at compile time instead of making a manifest array construct in the language definition, but I digress.
Creating new language features to solve specific optimization problems isn't always a good idea, that's what caused the C preprocessor. I see a need for adding features to a class without modifying the class and I see it should be done differently than static methods.
- --Peter gummer 00:34, 19 March 2008 (PDT) Paul mentions the word "optimize" twice in the article: it's only a potential bonus of his proposal, not the principal motivation. And his proposal isn't for "static methods" (although it does provide a mechanism for them: a frozen objectless routine would be equivalent to a "static method", I suppose). Your "implicit class" idea is interesting, but it's not clear to me how it addresses the concerns that Paul raised.
- -----paulb 15:58, 1 October 2008 (PDT)
Actually any call made using the Eiffel static calling convention would be performed statically instead of dynamic dispatch. In that case objectless calls need not be marked frozen
in order to benefit from performance gains. And, although performance is not the generalized issue here, I would like objectless calls to be implemented for performance reasons also. There is a dramatic increase in performance when using static calls as apposed to dynamic dispatch.