As we all know, nim seperates the procedures that operate on an object from the data of the object itself. I understand the thinking and largely agree with it.
However, a side-effect of this is that when importing an object from a module, it is difficult to control what is being imported. Such control is useful, IMO, not because of avoiding name conflicts (though that is nice also); but avoiding blanket imports makes debugging faster and easier. It makes a visual scan of the code much more obvious from any context, even from dead-tree paper printouts.
For example, if someone showed me the following code because they had problem with x:
import a_package
import b_package
var x = Joe()
x.something()
All I know is that Joe is a class from somewhere. I now get to do a hunt-and-find exercise. Or I need to get myself to a computer, install the packages and source, and look at it with a nim-enabled editor.
Alternatively, the imports could have been:
import a_package as a
from b_package import nil
The user would then prefix everything with the module name or alias. This totally works, but it gets very tedious the bigger the program becomes.
Another alternative is:
from a_package import Joe, something, something_else, ...........
This also works, but if Joe has 50 methods, then I either import everything as a huge dump. Or, I import just what I need and keeping adding/removing stuff as needed. Again, tedious, but it works.
My suggestion: add an indicator to do well defined import of an object and any associated methods found.
So:
from a_package import @Joe
or perhaps
from a_package import Joe.*
The key here is to provide the compiler a way to automatically import both Joe and any non-generic method or proc in the form of "method SSSS*(self: Joe, ...". In other words, is it a wild card that finds any proc who's first parameter is the designated class and conforms to UFCS usage.
So, the example program would become:
from a_package import @Joe, @Larry
from b_package import process_y, @ZZ
var x = Joe()
x.something()
Now I know exactly where Joe came from by just looking at it.
While, I'm still a newbie with nim, I've been doing Python programming for six years. I've come to appreciate the Python communities dislike of "from X import * " (the equivalent of "import X" in nim.) It has saved me countless headaches over the years. Ironically, my background before that was in C/C++ and other languages that embrace such open imports.
I suspect, if something like this were implemented, much of the nim community would also eventually use such a feature as a default way of doing things.
Thoughts?
This will also have further benefits for how to do symbol bindings in generics later on, if a type implements an == and hash the from x import T syntax should not lead to the hiding of these operations. Plenty of operations are affected by this, they should be more like override than like overload.
Araq,
I like your idea even better. I'm not involved in the compiler coding...is this easily implemented while keeping the method and data separation underneath?
It's reasonably easy to implement, but conceptually it muddles the method/data separation. The syntax keeps this separation though and that's good enough for me. :-)
So, just to be clear: This is a pure name lookup change in the compiler, no syntax changes required.
Oh and generics will cause some problems, we have to try it out...
@Araq
Well, I don't think it's possible to use generics in this solution. As far as I know, Rust's trait objects are implemented in a similar manner and they lack generic methods (that's one of the reasons why it's often advised to use generic traits rather than generic methods within non-generic traits).
As for data/method separation... sadly, you won't be able to split these into more than one module. :-/ However, it would be entirely possible to write a macro (quite a simple one at that!) to automate what you said. It would probably look like this:
class Shape:
...
proc hash = ...
proc area: float = ...
class Square of Shape:
...
proc hash {.override.} = ...
proc area: float {.override.} = ...
proc side: float = ...
let squareShape: Shape = Square(...)
echo squareShape.area # here, the square variant is called because area _field_ is set to square's one
echo squareShape.side # compile-time error, of course
Have in mind that it makes the values quite big and using a separate virtual dispatch table could be a better option.
Oh, there is one problem here: you can't overload routines (without another import, that is, otherwise you can create a method proxy). :-(
Oh, probably. I thought that by "attach" you mean:
type Shape {.inheritable.} = ref object
...
area: proc(Shape): float
type Square = ref object of Shape
...
proc newShape(...): Shape =
...
area = areaShape
proc newSquare(...): Square =
...
area = areaSquare
It does have some advantages (although not so many if one doesn't fancy object extension, like in Scala, let's say).