Consistency, Templates, Constructors, and D3
foobar
foo at bar.com
Mon Aug 27 14:06:12 PDT 2012
On Monday, 27 August 2012 at 20:22:47 UTC, Era Scarecrow wrote:
> On Monday, 27 August 2012 at 14:53:57 UTC, F i L wrote:
>> in C#, you use 'new Type()' for both classes and structs, and
>> it works fine. In fact, it has some benefit with generic
>> programming. Plus, it's impossible to completely get away from
>> having to understand the type, even in C++/D today, because we
>> can always make factory functions:
>
> I'm sure in C# that all structs and classes are heap allocated
> (It takes after C++ very likely) that's the simplest way to do
> it. You can do that in C++ as well, but other than having to
> declare it a pointer first. In C++ they made structs 'classes
> that are public by default' by it's definition I believe.
> Considering how C++ is set up that makes perfect sense.
>
>> FooType newFoo() {
>> return new FooType( ... );
>> }
>>
>> void main() {
>> auto f = newFoo(); // struct or class?
>> }
>
> By looking at newFoo I'd say a class; But if like in C# I'm
> sure you can't tell the difference (But C++ with the pointer
> you can). And for factory functions I'd put them inside the
> struct/class, unless I had a compelling reason not to.
>
> class Record {
> static Record newRecord(string options) {
> Record rec = new Record();
> // stuff building the complex record
> return rec;
> }
> }
>
>> However, I do agree it would be nice having some kind of
>> distinction between stack/heap allocation and copy/ref
>> semantics. Because constructor names are completely arbitrary
>> in my proposal, I think you could easily just choose a
>> different name (say, 'new' vs 'set').
>
>> struct Foo {
>> this set() { ... }
>> }
>>
>> class Bar {
>> this new() { ... }
>> }
>>
>> void main() {
>> auto f = Foo.set( ... ); // stack/copy
>> auto b = Bar.new( ... ); // heap/ref
>> }
>>
>> Again, this is just an arbitrary distinction and wouldn't be
>> enforced, so third-party libs could choose something
>> completely different... but then, they always could (like
>> above) so it's benefit is debatable.
>
>> I've had ideas before about having two different '=' operators
>> for assignment and copy, but honestly, I think just looking up
>> and understanding the types you're working with might be the
>> best solution. A task much easier with proper IDE tooltips and
>> the like.
>>
>>
>>> Would new still be a key word?
>>
>> No. You could just as easily name your constructor
>> 'freshCopyYo()'. In fact, you often want multiple constructor
>> names for different constructing procedures, like I explain in
>> my original post. For instance if you're loading from a file,
>> having a 'load()' constructor makes more sense than 'new()'.
>> When converting from some other type, have a 'from()'
>> constructor. The name implies the action, but they all
>> "construct" the type:
>
>> class Text
>> {
>> this new() // blank
>> this new(string s) // basic constructor
>>
>> this from(int i) // convert from int
>> this from(float f) // convert from float
>>
>> this load(string filename) // load from file
>> }
>>
>> All of these are constructors, because they're return type is
>> 'this'. They all implicitly allocate an object and have a
>> 'this' reference. However, their names and implementations are
>> completely arbitrary, which is a good thing because we need
>> and use these arbitrary constructors all the time today, we
>> just have to do it in a completely inconsistent way (static
>> factory functions).
>
> And a postblits would end up being...? The extra 'this' makes
> it look like an obvious typo or a minor headache.
>
> this this(this){} //postblitz?
>
> Honestly I kinda like it how it is now. It's fairly clear and
> concise. Only if you start getting creative can it begin to get
> confusing; Then again in any language someone who decided to
> make it confusing would make it confusing regardless.
>
> --
>
> enum JOJOJO = 100;
>
> class Jo {
> int jojo1;
>
> Jo jo(Jo JOO) {
> int jojo = 10;
> int joJo = JOJOJO;
> Jo JO = new Jo();
> // and so on and so forth.
> }
> }
>
> // Make a new Jo!
> Jo newJo(){
> return Jo.jo(null);
> }
>
>
> Jo something = newJo(); //now is this a class or a struct :P
> Right back at you.
>
> --
>
> Seriously... Actually if you get it confusing enough you could
> submit it to The International Obfuscated C Code Contest.
> http://www.ioccc.org/
>
> I would say if 'new' is part of the function name, it's
> returns a class. If it's referenced (or a pointer), it could be
> either. But not having a universal constructor would make
> creating an array with defaults impossible without
> declaring/hinting which one to use. It's one reason structs
> have to have good contents for all it's data members at
> compile-time, so you could create an array with defaulted
> information that works.
>
> If we take your approach and suggestion, which one should the
> compile assume?
>
> Something globalSomething;
>
> class Something {
> this defaultConstructor();
> this duplicate(); //or clone
> this copyGlobalSomething();
> this constructorWithDefault(int x = 100);
> }
>
> By signature alone... Which one? They are all legal, they are
> uniquely named, and they are all equal candidates. Order of
> functions are irrelevant.
FiL's scheme looks backwards to me. One of the main drawbacks of
factories is the fact that they have non-standard names.
Given a class Foo, How would I know to call a factory newFoo?
More generally, given template:
T create(T)(...) {
// .. how do I create an instance of T?
}
The correct scheme would be (as implemented in a few languages):
1. The constructor needs to be split into two separate stages -
creation/allocation and initialization.
2. Creation is done via a regular (virtual) method with a
standardized name ("new").
3. Initialization is done via a non-virtual function, e.g what we
call a c-tor.
The compiler/programmer know that creation is done via "new" and
that new must call a c-tor. If there is no c-tor defined one will
be generated for you by the compiler (same as it is now). In
addition, a default creation method will also be generated by the
compiler if it is not defined by the programmer. All it will do
is just forward to the init function (aka c-tor).
The main difference is that now the programmer has control over
the first stage (creation) and it is no longer implicitly
hardwired.
Example use case:
class LimitedAccount : Account {
// "regular allocation" - on GC heap
private Account new(Person p) {
return GC.allocate!LimitedAccount(P);
}
// init
this(Person p) {...}
...more code...
}
class Bank {
Account new(Person p, AccountType t) {
switch(t) {
case AccountType.LIMITED: return LimitedAccount.new(p);
... more cases...
}
}
}
// usage:
Account acc = Bank.new(PoorShmoe, AccountType.LIMITED);
More information about the Digitalmars-d
mailing list