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