Class extension (dot syntax for new methods)

BlitzMax Forums/BlitzMax Programming/Class extension (dot syntax for new methods)

Blueapples(Posted 2007) [#1]
Hey, I was wondering if there is a possibility to have a feature added to the compiler.

In RealBASIC, which I otherwise do not really like that much, they have an interesting concept where you can specify that a method is to be called with a "dot syntax".

This ends up looking something like this, as an example I'm currently using standard functions to accomplish:

Function DecodeString(Map Extends TMap, str:String)
...
EndFunction

Function EncodeString:String(Map Extends TMap)
...
EndFunction


Then, to call these new "methods", you woulds simply use an existing TMap object, and call the functions as if they were included in the built in definition of TMap:

Local Data:String = ReadData()
Local Values:TMap = New TMap
Values.DecodeString(Data)
MapInsert(Values, "NewKey", "NewValue")
Data = Values.EncodeString()
Notify Data


I think this would also be really handy in developing AI modules, and a lot of other cases more focused on "games", though you might find that once you have this in mind, a lot of possiblities occur to you.

You might ask why don't I just subclass TMap and be done with it? Well, the problem is that existing functions and libraries are going to return TMap objects to me - I can't subclass because there is no way to "cast down the chain". I can't take a TMap and get a TBAMap without copying the object to a new instance of my own class.

Also, this syntax would allow module authors to provide whole packs of functions that would just appear on existing classes (TMap, TGadget, TBank, etc.) without *any* change other than simply Importing the module.

Sounds pretty cool huh?


tonyg(Posted 2007) [#2]
Isn't that a Method in BlitzMax?
<edit> Although, on re-reading it, I could be very wrong.
<edit2? Why can't you cast tmap to tbamap?


Dreamora(Posted 2007) [#3]
Realtime Extension is not existing in BM nor any compiled language that prefers performance over "features that allow unclean code design"


Something either is of a class (or extends it) or it isn't.


But what you would do in your case is quite simple as well:
Decoration.

make your classes extend TMap but as well give the extended class a field map:TMap

now you create a new object of that class and assign the return of your library method / functions to the map field.

This is a parade example for decoration actually


Blueapples(Posted 2007) [#4]
tonyg,

Almost! It's basically a way to add a method to an existing class after the EndType statement. A short example might be:

' Some class provided by BRL or a library:
Type TBuiltIn
  Method BuiltInMethod()
  EndMethod
EndType

' In my own program, I can add a method to it without subclassing:
Function ExtendedMethod(BuiltIn Extends TBuiltIn, S:String)
  Notify S
EndFunction

Local BuiltIn:TBuiltIn = new TBuiltIn
BuiltIn.ExtendedMethod("Calling the extended method")




Dreamora,

A couple things. First, I'm not proposing any kind of real time extension at all, there's nothing dynamic going on here at all. It's basically syntax sugar that the BMK compiler would add to allow someone to write A.f(B) instead of f(A, B).

The gains are conceptual. It doesn't actually give you any dynamic functionality, you can't add a function to an existing object at run time.

There aren't any performance penalties since both of these syntaxes would boil down to exactly the same ASM code.

Creating a new class every time I want to add a single method to an existing class seems kinda of silly. It also precludes the option for third parties to provide extended sets of methods to existing classes, which a programmer could then simply begin calling without any other changes to their code.

A programmer might even want to have their own private set of extensions that they often use with built in classes. I have a ton of functions that oeprate primarily on a single existing class that I did not write. I have to use the non-object oriented syntax to call these functions. Creating these as methods in subclasses doesn't make sense because, as I said before, I can't directly use my custom functions on objects returned by other libraries or BMax built in functions.

If those extensions were provided as entirely new classes, the programmer would always have to create new objects and copying the contents of their existing objects (TMaps in my first example) to the new classes. I realize this is possible but it is quite inconvenient.

Those new classes would also have to redirect every method of the base class, as you proposed. This is not "decoration", and I have seen it before.

That used to be the only way to get "inheritance" in VB6. I can't believe anyone would actually suggest this as a realistic option. What if the definition of TMap adds a new method in the next release? I have to go into my class and make sure I override that method and redirect it to the real TMap? Ridiculous. What if I sold that class to other people as part of a library? They suddenly don't get to use the new method anymore, without either waiting for an update from me or adding their own redirect method.


nino(Posted 2007) [#5]
I'm not sure I get what you're after entirely but the way that I add methods is via function fields. This often comes up for me when I am writing a low level class that needs access to higher level functionality e.g. my sprite collection needs to somehow report an error but displaying text is something done higher up by a set of gui classes...


Type Tsprite
Field x,y,id,etc'...
EndType


Type TspriteCollection Extends TList
Field _id:Int
Field error(msg:String)
Method put(s:Tsprite,x:Int,y:Int)
	_id:+1
	s.x=x
	s.y=y
	s.id=_id
	AddLast(s)
EndMethod
Method get:Tsprite(id:Int)
	For Local s:Tsprite=EachIn Self
		If s.id=id Return s
	Next
	If error error("Could not find sprite "+id)
EndMethod
EndType



then in my gui class or higher level whatever I simply set error to something that would take care of that...

Function showError(msg:String)
	Notify(msg)
EndFunction


Global mySprites:TspriteCollection=New TspriteCollection
mySprites.error=showError
mySprites.get(999)


Sorry if I misread what you were after..


Blueapples(Posted 2007) [#6]
nino, kind of but not really.

Two important differences:

1) You can only provide custom functions for a limited set of function field names, so you need to know all those names in advance. Useful, certainly, but not useful for the same kind of problem as I have.

2) Your custom functions only apply to an inidivudal object. What I'm proposing would instantly become available to all instances of the class being extended.


Blueapples(Posted 2007) [#7]
tong,

As to your second question, honestly I'm not sure why this kind of casting is not allowed. It has something to do with the way that objects are implemented internally in BMax. For instance, this code will not run:

SuperStrict
Type TBAMap Extends TMap
	Method Encode:String()
		Local Result :String
		Local K:String
		For K = EachIn Keys()
			Result = Result + String(K) + "=" + String(ValueForKey(K))
		Next
		Return Result 
	EndMethod
EndType

Local map:TMap = New TMap  ' Change to map:TMap = New TBAMap and it will work
map.Insert("test", "test value")
Notify TBAMap(map).Encode()


It gives the runtime error "Unhandled Exception:Attempt to access field or method of Null object".

Some languages do allow casting in this direction as long as there are no fields added by the subclass, and even sometimes then. This isn't really what I would want, as I believe method extension provide much more flexibility.

It'd be much better to be able to call .Encode() directly instead of always having to cast to TBAMap() first, even if that worked.


Who was John Galt?(Posted 2007) [#8]
I can understand where this may be useful, but it makes for confusing code. i.e: Doh! Where are those methods?


tonyg(Posted 2007) [#9]
... but what's wrong with saying
local map:TMap = new TBAMap?


Dreamora(Posted 2007) [#10]


1) You can only provide custom functions for a limited set of function field names, so you need to know all those names in advance. Useful, certainly, but not useful for the same kind of problem as I have.


Thats exactly something that is NOT possible in a compiled language.
Either you know upfront what you are after or it will not work outside a scripting language.

So if you do not know what is passed in you must wrap that into an object that you pass in and then either extract the information from that one or let it perform a specific task to use the information straight away.


On your answer at the "top":
I fear you do not understand that such a "sugar" can not work within a compiled environment out of the box.
Such a thing (which is dynamic extension, you try to take a TMap and dynamically *at runtime* extend its capabilities to those of TBAMap) is heavily reflection based and reflection is slow.

A programmer might even want to have their own private set of extensions that they often use with built in classes. I have a ton of functions that oeprate primarily on a single existing class that I did not write. I have to use the non-object oriented syntax to call these functions. Creating these as methods in subclasses doesn't make sense because, as I said before, I can't directly use my custom functions on objects returned by other libraries or BMax built in functions.


Show me sample code that shows exactly this problem and I really mean exactly this problem not some kind of strange problem related in some way with it.
And especially show me how the usage of decoration does not work in that case, because I can simply show THAT it works!


SuperStrict
Type TBAMap Extends TMap
  ' Decoration class to extend TMap without changing its implementation or directly extend it.
        field map:TMap

	Method Encode:String()
		Local Result :String
		Local K:String
		For K = EachIn map.Keys()
			Result = Result + String(K) + "=" + String(map.ValueForKey(K))
		Next
		Return Result 
	EndMethod
EndType

Local map:TBAMap = New TBAMap  ' Change to map:TMap = New TBAMap and it will work
map.map = new TMap
map.Insert("test", "test value")
Notify map.Encode()


That code does not care how the library object is implemented or anything the like. It only takes it to perform operations on it.
You could even use that to implement features of a class that the decorator wasn't even extended of.
For example you define an Abstract A with the given abstract methods you need.
then you have B extends A with full implementation of it
and then you take C extends A with a field "A" to which you assign an object of class B (in this example). Now you can reimplement the whole set of features (or just call that object original implementation).
there is no need you would even want to Cast from A to B if the stoerd handled was not a B upfront, if you use a decorator

The stream wrapper classes of BM by the way are all decorators as well and not pure extensions.

If you are interested in really learn how to use the real power of OO programming instead of trying to get script like approaches into it, I suggest reading a book on Design Patterns.
I personally prefer "Head First: Design Patterns" above all others I've read on the topic


Koriolis(Posted 2007) [#11]
I have only read a few words here and there, but it seems most people don't understand what the feature request is all about. So in the interest of clarity, I'll allow myself to point to wikipedia:
http://en.wikipedia.org/wiki/Extension_method

To sum up, extension methods cannot be overriden and are much more like plain functions, they are just a syntactical sugar that can be quite handy on some occasions.
[Shameles plug]And because it is handy, that's something I have implemented in BriskVM[/Shameles plug]


tonyg(Posted 2007) [#12]
That's clearer.
What is different than a function which takes an object instance as a parameter though? There might be some deep down 'something' but doesn't it allow the same thing in BlitzMax if, for example, you have been given compild source?


Blueapples(Posted 2007) [#13]

Such a thing (which is dynamic extension, you try to take a TMap and dynamically *at runtime* extend its capabilities to those of TBAMap) is heavily reflection based and reflection is slow.


Sorry, somehow I am not being clear. I usually pride myself on clarity in writing but obviously I'm not doing so well in this case.

I totally agree with the above statement, however what I am proposing happens at compile time. There is no runtime injection of methods or other code. If that were the case, you would be right to critisize my request in the context of BlitzMax. What I'm talking about is something that affects only the compiler and syntax, and has no bearing whatsoever on the runtime itself (which is where dynamic method dispatch has to be handled in scripting languages). So I think we're on the same page as far as the rest of your argument goes, I agree. However that isn't what I'm asking for. ;)


Show me sample code that shows exactly this problem


As a real world example, MonkeyBreadSoftware (http://www.monkeybreadsoftware.de/) sells a package of extension methods for REALBasic that work out of the box with existing code, on the built in classes that are provided by REAL as part of their runtime. You simply begin using the new methods as if they had always existed: no need to cast or subclass or wrap objects in "decorators".


If you are interested in really learn how to use the real power of OO programming instead of trying to get script like approaches into it, I suggest reading a book on Design Patterns.


I was a Computer Science major, so I already understand this material. Please realize that there are other ways of looking at problems than the way you were trained or studied. Not all solutions were known in the 1960's, nor when I was in school, nor are they all known now. Extension methods are a new language feature in a few OO languages and they have their place in solving certain kinds of problems.


So in the interest of clarity, I'll allow myself to point to wikipedia:
http://en.wikipedia.org/wiki/Extension_method


I guess I should have done that to begin with. I want to be clear that this is not a dynamic language or scripting language feature, it is something that does exist in a compiled language already, REALBasic.


That's clearer.
What is different than a function which takes an object instance as a parameter though? There might be some deep down 'something' but doesn't it allow the same thing in BlitzMax if, for example, you have been given compild source?


There is no difference. I believe you understand the mechanics of what I'm proposing perfectly. As I tried to say before, both syntaxes should compile down to exactly the same binary code.

However, the gains are in the way that you are allowed to think about code given this kind of syntax.


Now I've been looking around in the BMK source for where this might be implemented and for some reason i can't actually find the parsing routines. There's a lot of prep word, but I can't actually find, for instance, the function that handles a Function predicate. Anyone looked this before? Is that part of the code not available to us?


Blueapples(Posted 2008) [#14]
Here's a good explanation of a similar feature, found in Objective-C:

http://en.wikipedia.org/wiki/Objective-C#Categories

Categories

Cox’s main concern was the maintainability of large code bases. Experience from the structured programming world had shown that one of the main ways to improve code was to break it down into smaller pieces. Objective-C added the concept of Categories to help with this process.

A category collects method implementations into separate files. The programmer can place groups of related methods into a category to make them more readable. For instance, one could create a "SpellChecking" category "on" the String object, collecting all of the methods related to spell checking into a single place.

Furthermore, the methods within a category are added to a class at runtime. Thus, categories permit the programmer to add methods to an existing class without the need to recompile that class or even have access to its source code. For example, if the system you are supplied with does not contain a spell checker in its String implementation, you can add it without modifying the String source code.

Methods within categories become indistinguishable from the methods in a class when the program is run. A category has full access to all of the instance variables within the class, including private variables.

Categories provide an elegant solution to the fragile base class problem for methods.

If you declare a method in a category with the same method signature as an existing method in a class, the category’s method is adopted. Thus categories can not only add methods to a class, but also replace existing methods. This feature can be used to fix bugs in other classes by rewriting their methods, or to cause a global change to a class’ behavior within a program. If two categories have methods with the same method signature, it is undefined which category’s method is adopted.



The Fragile base class problem is part of what I was trying to explain: http://en.wikipedia.org/wiki/Fragile_base_class

While implementing this as a runtime extension wouldn't really fit into BM, it could easily be implemented as compile time extensions to the method table for the class being extended. Then, as they point out, we can make bug fixes and custom changes to existing classes without worrying about the next release blowing away our changes. I just had to do this in my list based interpreter so I could have a custom TCobaltList. It would have been so much cleaner and transparent to simply add my custom functionality to the base TList and only include it in the programs where I wanted to use it.

I really think BlitzMax would benefit from having this kind of functionality.


FlameDuck(Posted 2008) [#15]
Thats exactly something that is NOT possible in a compiled language.
Sure it is. There are plenty (well SOME) compiled languages that offer duck typing.

I really think BlitzMax would benefit from having this kind of functionality.
I agree and I think you made an excellent case for why.


Dreamora(Posted 2008) [#16]
the BMK does not do the compile. Compile is done in BCC.
BMK only handles the "higher level" interconnections, namely preprocessing and setting up the c++ compiler.

To add your feature you would basically have to extend BMK to the point where it would predict and basically do a full precompile.
Possible: Yes
Simple: No
Basic concerns: Much more complex compilation which results in larger compile times. Quick Build basically would need to be dropped as well.

As well, OO wise it would be a nightmare to do something like that. It breaks with the basic idea of encapsulated functionality with guaranteed behavior, if you can inject stuff at compile time.

What blueapples there describes would be a way to do it and a way I would actually welcome. But its exactly what you didn't want, runtime behavior, which in the end means reflection or function pointers which take the instance as first argument (script like attempt).
Categories potentially could be done by modifying the BMK.
Currently you would need to add a call method that parses out the stuff for you and calls a function (whichs name and function pointer are stored within a TMap).


the Wikipedia Article on the C# implementation actually shows the main problem of it as well. It makes compile time syntax checking basically worthless if you can have twice the same method defined for the same object without it beeing an override one.


Koriolis(Posted 2008) [#17]
I think there is still confusion. The request is for a completly compile time, syntax sugar, feature.
Dreamora, you say
What blueapples there describes would be a way to do it and a way I would actually welcome
Blueapples being the orignal poster, I'm not sure what you're arguing against then.

As well, OO wise it would be a nightmare to do something like that. It breaks with the basic idea of encapsulated functionality with guaranteed behavior, , if you can inject stuff at compile time
Given that the feature is entirely compile time, this statement is out of place. It breaks nothing, behaviour is still determinist, and by defining new extension methods you can't affect the behaviour of another module using the type, unless of course you create an extension method with the same name as a plain method (note: this may simply be forbidden, or you may give higher priority to plain method when doing the lookup), AND recompile the said module.

It makes compile time syntax checking basically worthless if you can have twice the same method defined for the same object without it beeing an override one.
Syntax checking does not become suddenly worthless, as the compiler still has the full information at compile time. The only real concern is for humans: it becomes non-obvious when you're calling a "true" method and when you're calling an extension method. Non obvious, but well defined and determinist. But that's also mainly the whole point.


ziggy(Posted 2008) [#18]
Imagine I create module B wich 'adds functionality' to the existing TList object, without extending it, using your suggested method.

Ok, then imagine somebodyelse creates a module C wich also adds functionality to the existing TList object without extending it. funnily, the module B and the module C add a method called DoStuff to the base brl.linkedlist.Tlist object.

Then I create a project and I want to import those modules at once. but, oh surprise, they have duplicated name definitions for a method on the base class, So this way of doing things break completelly namespaces... no fun...

If you're thinking on categories, IMHO it whould have sense on a OO language that supports overloading, shadowing and overriding.


Koriolis(Posted 2008) [#19]
It doesn't break anything, you just have a duplicate identifier error, just like when you do this with functions. It doesn't compile, that's it.
It makes sense, extension methods are just plain functions. You can't import two modules that defines two homonym functions and expect it to work. It's jno different here.

Special support can be added by allocating a private namespace for each type (making the feature much more usefull as otherwise you'd be unable to have two homonym extension methodds for different types), but even in this case it works pretty much like for plain functions, only within the private namespace rather than at the global namespace.


ziggy(Posted 2008) [#20]
You can't import two modules that defines two homonym functions and expect it to work. It's jno different here.

Yes you can, in fact, it works wonderfully. You may user moduleserver.modulename.function to access it.

As an example:

local P:xxx.yyy.tlist = new(xxx.yyy.tlist)
Local L:brl.linkedlist.tlist = new(brl.linkedlist.tlist)

P and L are different object from different modules with the same name. This is namespacing and it works great unless you start extending things from outside the modules.

As an example, you can create a module with the function Graphics if you want, and import it anywhere. just you may use:

mymodserver.mymodule.graphics("hello")

and

brl.max2d.graphics(800,600)

to diferenciate them.

Allowing 'outside the module' extension is the quickest way to break namespacing


Dreamora(Posted 2008) [#21]
Koriolis:
Well perhaps I totally your idea but:
Modules are compiled in a totally distinct step to the extension methods. Therefor you simply can not handle extension methods at compile time.
The extension method does not exist at the compile time of the module and thats the only time BCC checks the implementation details of the classes of this module! This is the very basic concept of encapsulation and modular code. So doing anything on the class at compile time is out already.

What would be possible is dynamically bind it at runtime against the class using internally a reflection like or script callback like scheme (reflection because the "extends" part is only doable that way). But this would mean considerable modifications of BBObject, the base class behind anything, to allow such a thing to happen. And I'm not sure how complex the handling and GC end of it would become. (assuming it took MS about 8 years to design and implement it, I would guess nightmare is not even near reality)

I don't say its a basically bad feature. It would open up interesting possibilities.
But out of my sight it seams to break with a few of the elemental OO paradigms and ideas the way it is suggested and used in RealBasic, thought that does not really puzzle me, its an Apple originated language and those end normally has a different view of the reality. (The problems: it produces non clean code as an object can be extended after it was "fixed" which would break programming by contract designs, it would allow to totally mess up the programming to interface designs and a few other problems).
So it would need a clear specifications and clear restrictions on where you can apply it to (not to final / abstract classes obviously. You can not override existing functionality, as BM does not allow overrides at all *doing it in extended classes is redefines/reimplements the method similar to how fields work*). Then it would surely make a great feature.


Koriolis(Posted 2008) [#22]
Yes you can, in fact, it works wonderfully. You may user moduleserver.modulename.function to access it.
OK, maybe i should have been clearer : it won't work unless you give an explcit scope. There is no reason why it would be different with extension methods, because extension methods are just plain functions.


Allowing 'outside the module' extension is the quickest way to break namespacing
When I ad a new function, it's necessarily outside of any other module. What's different with extension methods? Once again, extension methods are for the most part a simple syntax sugar that may even be implemented by a preprocessor. There are reasons not to like it (specifically, amking not obvious for huma reader when you call a real method, and when you call an extension methods), but 'namespace breaking"' is not one of them.

Well perhaps I totally your idea but:
Modules are compiled in a totally distinct step to the extension methods...
Indeed I think your misleaded here. Maybe a simple example will help to outline how simple and dumb the feature is.
Just think about the langauge that don't suport proper OO like BlitzBasic. In such languages, you can't do "Object Oriented Programmin", but you can do "Object Based Programming", such as in this example:
Type TBla
End Type

Function TBla_DoSomething(this:TBla)
...
End Function

Local obj:TBla = new TBla
TBla_DoSomething(bla)

Here we hare somewhat emulating the concept of methods. It is not a "true method" in the sense that you can't do any overriding, and yes indeed it's just a function. However you're thinking OO. Now, what if there was a syntactic sugar so that I could use this natural syntax:
bla.DoSomething()
It surealy feels more what you intended to do in the first palce. However it's still a pure syntactic sugar, because you still have a simple function (it's still a direct call, there is no dynamic dispatch, and no possibility of overriding). Extension methods are just that.
Given that it's just a plain function, I hope it's now clearer that there can't be much more technical problem than there is (or isn't) already with good old functions.



The problems: it produces non clean code as an object can be extended after it was "fixed" which would break programming by contract designs, it would allow to totally mess up the programming to interface designs and a few other problems).
Nope. You're not modifying anything in the existing contracts, so there's no problem. Being a simple funciton, an extension method is not supposed to ba able to bypass anything. Let's imagine for a second that BlitzMax supports private fields and functions, an extension method couldn't magically access these private parts any more than any other function. You don't touch anything in the original type itself.

In a sense, I can easily see why extension methods seem bad. We've learnt how important encapsulation and contracts are, and extension seem to break all that. The thing is they don't. At all.

Really, the one good reason for not liking extension methods is it makes calls more ambiguous for the human reader, in the sense that you may wonder if it's an extension method or a real method. I don't see any other reason.


ziggy(Posted 2008) [#23]
Yes it was clear, the problem remains the same. It is not a good idea to be able to add a method from outside the type module in the current state of BlitzMax.

Let me elavorate your example:
module1.bmx;
Type TBla
End Type


then Module2.bmx:
Function TBla_DoSomething(this:TBla) Extends module1.Tbla
...
End Function


Now I use a 3th party module that has this code:

Module3:
Function TBla_DoSomething(this:TBla) Extends module1.Tbla
...
End Function


I've managed to create two incompatible modules. And thats not a good scenario, completely impredictible, as it can be caused even by an update in the BlitzMax core base types. so... It would be possible, sure, clear... not so sure, reliable: IMHO sure not.

[EDIT]I've been thinking of it and, maybe in a non-module application this scenario could not happen so, with this restriction, it may be much more suitable, what do you think?


Dreamora(Posted 2008) [#24]
Well, I sadly don't think you can implement it outside the BCC. Reason is that this one "seals" the classes. I've already tried that before reflection was done to get features "hacked" in, after I got private and public within classes to work.
But it ended with accepting that Decorators are faster, simpler and cleaner.
My main target, btw, was it do add "implements" like support for Type -> multiple Type Abstract
Now with reflection I can basically do something like that.

My main problem with the extension method is exactly what Ziggy points out. On that end the compiler can't do anything against that anymore. That would be a linker job, not sure if LD will going nuts about it.


Koriolis(Posted 2008) [#25]
@Ziggy: Why would there be more incompatibilities than for functions? It's really simple. If having two functions with the same name in two different modules is no problem, then the same applies to extension methods. There is no problem as long as you don't call the said extension method (just like for function), and if it happens you need to, the compiler could let you specify which extension method to call (again just like for plain functions). And to be honest I would not be very concerned if the compiler wouldn't give you this possibility, and simply pick the "closest" function (sorry, extension method) as usual. I say as usual because it's already doing it. When you're in a type by example, a type function can shadow a global homonym function in the same module, which itself can shadow another global homonym function in another module. The compiler will pick the "closest" one, that is the one in the current type. That feels totally natural to me. And guess what, there is no reason for extesion methods to be any different. And as a side note, when calling a function from inside a type method, you can't tell from that line alone if it's a method call, a type function call, or a global function call. So even the potential ambiguity I admitted a few post ago is actually nothing new. You already live with it.

@Dreamora: I fear that from the very beginning you don't want to see it's a compile time syntactic sugar. No reflection neeeded, no hack, no modification to the linker, NOTHING. Just adding a new syntax for calling some functions to make it look like a method call. To be totally honest, to make it really perfect you'd need to use different namespaces for extension methods, but that's once again nothing new in itself, and nothing problematic.

I'm really starting to paraphrase myself, so I'll just state one very last time the main issue: extension methods are just like plain functions, don't let the name "extension methods" fool you.


Fr0sty(Posted 2008) [#26]
This just seems untidy to me and a bit of a shortcut that could turn nasty. Anyone who's tried debugging code in C++ that makes extensive use of preprocessor macros will know what I'm talking about :) Somehow this reminds me of the same thing. Syntactic sugar is nice when you're there coding it, but not when it's six months later and you're debugging it (whether it's your code or somebody elses').

All of a sudden, the simple statement:

MyCarClass.UpdateMovement()

suddenly becomes very unrelated to what's actually happening under the hood.

The fact that this feature has taken so many forum posts just to even describe properly should be ringing alarm bells alone.

I even have issues with the fact they're called 'extension methods' at all. Koriolis, you say "don't let the name "extension methods" fool you." That ambiguity shouldn't even exist to begin with.


Koriolis(Posted 2008) [#27]
I sort of agree. However, the number of posts it takes to describe the feature is certainly not an indication of how simple or how complex it is. It's just new to a lot of people. However simple and dumb this feeature may be, people necessarily get confused when they start taking one thing for another.
And it may look untidy, but only from the point of view of someone who only wants to think in Java, C# or BlitzMax OO. Many purely functional languages have nothing like real methods", just plain functions and everywhere, and they're in in may respects much more powerfull that said languages.

You say

All of a sudden, the simple statement:

MyCarClass.UpdateMovement()

suddenly becomes very unrelated to what's actually happening under the hood.
I think you're not realizing that it's already doing something very inrelated to what you see here. It's doing a dynamic dispatch, and you can't tell by that line which meethod implementation is going to be called, you only know it will adhere to the contract.
With extension methods, it's actually simpler given that the call is direct and without dynamic dispatch. It is actually what you do when you do Object Based Programming in a language that doesn't support proper Object Oriented Programming.


As a last note, I totally agree that macros (I'm talking C styla amcros) are the devil's tool to make you mad when debugging. However you can't seriously compare that to extension methods. A call to an extension method is just a normal call, debugging that is just as easy as anything else.

If all you're arguing against is the name, feel free to find a better one. I would actually like to have a better one, this would certainly help (and I may even use it in my own implementation). Truth is, we didn't invent the term, and that's what happens with programming languages : you tend to reuse existing termes because having dozens of terms for the same thing would only make MORE ambiguous.


Fr0sty(Posted 2008) [#28]
Good points. After doing a bit more reading on extension methods, I think I'm starting to come around to the idea :)

See here http://weblogs.asp.net/scottgu/archive/2007/03/13/new-orcas-language-feature-extension-methods.aspx


A scenario I can think of where I would want to use them is in implementing a savegame system where your game may be dealing with third-party types. You could use extension methods to add a .Serialize() call to all the types you needed. You could then have an extension library you can swap out for another - eg:

Include "XMLFile_SavegameSystemExtn.bmx"

then maybe later:

Include "Binary_SavegameSystemExtn.bmx"

All your Serialize() methods could then reside in the one place, while your code would still only have to call:

SomeGameObject.Serialize()


ziggy(Posted 2008) [#29]
Yes, unless you're using a third party module that already defines this extension methods for the same base types. Or even funny, if your importing two modules, both of them have a serialize method for the same base class. How do you know what are you calling when you type:

MyClass.Serialize()

isn't it funny?

Also, not to mention you break namespaces.

The method Modserver.Module.Type.ExtendedMethod
is not really scoped at modserver.module, so it can have duplicates, but compile, and you can spect a lot of issues there, unless a very strict rule is applied.

Korilis said the 'closest' one wins, but when both are in a third party module, there's no way to state wich is closest. So, really a good thing to keep in mind, but also a pandora's box as it adds some potentially dangerous scenarios.


Koriolis(Posted 2008) [#30]
How do you know what are you calling when you type:
And how do you know which function you call when you import two modules that define two function fo the same name. I don't knwo why you want it to be any different.

Korilis said the 'closest' one wins, but when both are in a third party module, there's no way to state wich is closest
Sorry if it looks impolite, but I'll have to quite myself here:
and if it happens you need to, the compiler could let you specify which extension method to call (again just like for plain functions)
Again, there is no firm need for it to be different than for plain old functions in this area. Why couldn't the compiler let you specify the fully qualified name of the extension method? The syntax may be ugly in such a case, but who cares, conflicts shouldn't happen often anyway.


Dreamora(Posted 2008) [#31]
Because there is no fully qualified name.
both have the exactly same header and type -> break at latest on link time.
Letting user decide is not acceptable and doing it through commands will not happen otherwise we would have head multiple inheritance with redefine, rename, undefine like eiffel for a year already.


Koriolis(Posted 2008) [#32]
Because there is no fully qualified name.
Err, what? Of course there is. What is "Modserver.Module.Type.ExtendedMethod" then ?

Letting user decide is not acceptable and doing it through commands will not happen otherwise we would have head multiple inheritance with redefine, rename, undefine like eiffel for a year already.
I'm not even sure you're replying to anything in this thread. Did you actually read the posts? THERE IS NO DYNAMIC DISPATCH. IT IS NOT A REAL METHOD. ALL THE ISSUES ARE COMPIILE TIME ISSUES AND NOTHING BREAKS AT LINK TIME (yes, I'm getting a bit tired with this, nothing personal. It's not like I'm trying to sell "extension methods").


ziggy(Posted 2008) [#33]
Err, what? Of course there is. What is "Modserver.Module.Type.ExtendedMethod" then ?

Modserver.module.type.Method references a method DEFINED IN THE MODULE SOURCE CODE, so it can't have any duplicated, and that way the module is always compatible with any other module, even if any other module has classes with the same name, and the same methods and the like.

Adding the possibility to add methods from another module breaks this rule. How would a extended method be considered part of the original class? Don't you see that we would be adding false methods to the original BaseModServer.BaseModule.BaseClass ? If we do that, the problem stated before is there. Problems at compile time. two or more modules could be adding the same method to a base class, and with the same namespace BaseModServer.BaseModule.BaseClass.ExtendedMethod. Obviously you could state that the extended methods could mantain the namespace of the module where they are created, and from where they are extending the original base class, but that would definitively make code even less clear and would break all the logic of a correct namespacing.


Dreamora(Posted 2008) [#34]
Koriolis: I'm trying to explain the why it breaks with a simple example that should make sense even then when you assume that I still don't get it.
And just to mention that: Anything between different modules is ALWAYS a link time issue. There is no compile time check for it! Modules Import commands is even a reference, not a source import commands like on executables! Thats why you need to deliver imported modules as well with your modules when they are not BRL standards. (as visible through the .i files as well)

so here the sample:

base.mod\baseclass.mod\baseclass.bmx
Module base.baseclass

SuperStrict

Type TSuperClass
  field someCoolThing:String
  field myNumber:int

end Type


base.mod\extendone.mod\extendone.bmx

Module base.extendone
Import base.baseclass

function tsuperclass_someExtendMethod()
 print "Some cool thing is: " + self.someCoolThing + " at number " + self.myNumber
end function


base.mod\otherextend.mod\otherextend.bmx

Module base.otherextend
Import base.extendone ' <- That is where the problem starts

function tsuperclass_someExtendMethod()
 print "Test"
end function


So, if I import base.otherextend and have

local t:TSuperClass = new TSuperClass
and call

t.someExtendMethod()

What is the uniquely defined output I get hmm?
Hope you get the point on which we try to raise you.
For BriskVM its easy. Its a script language. For the same reason this mechanisms are easy for other script languages.
A similar mechanism should be doable in Java as well as it has compile time mechanism that are not possible within a fully machine code compiled language.

But BM is not one of those. Already the simple fact that BM does not even support overloading will be the simple "no go" for this feature, no mather how interesting it would be to have it.

And given the fact that overloading was requested since its initial mac release, I would guess that this thread could continue till 2012 without any problems.

If you have an idea on how to get it on through BMK, let me know and I am happy to help there.
But as long as the word "compile time" appears in your instructions it just will not happen nor will it be possible.

And no, this is not to destruct this interesting idea. I spent 2 days on trying to get this in somehow by compiling modules, modifying interface files etc ...


ziggy(Posted 2008) [#35]
I agree with Dreamora, the idea is very good, but there are some dark areas, in case we could solve the compile time issue in order to mantain consitence between classes. I don't think we may spect to see this on BM becouse its architecture. BM is one of the best languages I've used, but it has some limitations we have to live with.


Koriolis(Posted 2008) [#36]
Oh dear. I really feel like I'm really bad at explaining very simple things. Every time you reply to me you're making points about things I already tried to explain.
If by chance I have the time and will this week end, I'll try to make it clearer. For now suffice to say that I really wasted too much time on this.
EDIT: To Dreamora: I'll just take the time to say that BriskVM is no different. Saying that it's a script language is really meaningless, because BriskVM is totally compiled, and doesn't use "duck typing". It uses static typing just like BlitzBasic or BlitzMAx, so it is in the very same position as BlitzMax, no difference here. However if you look further you'll see that I didn't implement the possibility to specify the fully qualified named for an extension method, but that's just an implementation choice.


Blueapples(Posted 2008) [#37]
I really couldn't resist coming back to this and correcting this misconception. I've adjusted the code from Dreamora's example to show what the compiler will see and why this is not a new issue.

"Extension methods" are nothing more than regular global functions that can be called with a different syntax that LOOKS like calling a method.

base.mod\baseclass.mod\baseclass.bmx
Module base.baseclass

SuperStrict

Type TSuperClass
  field someCoolThing:String
  field myNumber:int

end Type


base.mod\extendone.mod\extendone.bmx
Module base.extendone
Import base.baseclass

function someExtendMethod(self:TSuperClass)
 print "Some cool thing is: " + self.someCoolThing + " at number " + self.myNumber
end function


base.mod\otherextend.mod\otherextend.bmx
Module base.otherextend
Import base.extendone

' Compiler will choke at this point and say 
' Compile Error
'  Duplicate identifier 'someExtendMethod'
' Because someExtendMethod ALREADY EXISTS
function someExtendMethod(self:TSuperClass)
 print "Test"
end function