October 10, 2014
On Fri, 10 Oct 2014 21:45:32 +0000
dcrepid via Digitalmars-d <digitalmars-d@puremagic.com> wrote:

> Great, thanks for the tips!
you're welcome. ;-)

> Thanks for the effort of providing a proof of concept, even though I don't agree with what the data property should do. Shouldn't properties not mutate the data?
it depends. ;-) i think that doing such "lazy initialization" in property is acceptable. maybe not the best style, but acceptable. yet i may be wrong, of course.

> I think its a waste to check whether the buffer is there every time you need to access it.
this check costs almost nothing. and when you need buffer, most of the time you need it for some lengthy operation anyway, so checking cost can be ignored. and you can do `auto mypointer = a.data.ptr;` to avoid further checks. actually, it's meant to be used like this:

  auto d = a.data;
  // here we'll work with d, so no more checks

> Its better to allocate at the start, throw an exception if it can't be allocated, then provide access to it without any wasteful checks between. At least, this is the RAII type of idiom I'm after.
the main caveat with lazy initialization is that your program can fail at arbitrary place (where you'll first access .data), not when you declaring buffer this can be bad 'cause you may already execute some preparation code thinking that you have your buffer at hand.

> I've pasted most of the struct I've been using here: http://dpaste.dzfl.pl/15381d5828f8
will take a closer look at it later, i'm sleepy now. ;-)

btw, i'm used to call core.exception.onOutOfMemoryError when malloc() fails. We can't be sure that we still have memory to construct new Error object. sure, we'll lose linenumber info this way. it's not a rule of thumb, though (Phobos tend to throw new OutOfMemoryError instances, for example). but if we are working with memory on such low level...

> better methods to doing it safely, for sure. But to do the same with only a single pointer?
why do you insisting on having single pointer? sure you can use all sort of tricks to pack alot of data in single pointer, but i can't see any practical sense in it. today when smartphones have gigabytes of RAM, i'll trade some more pointers for ease of using and safety.

> I think I like the idea of the factory method now though, as I've learned you can hide a struct inside a function, and then call the function to set the struct up properly and return it. At least, I'm sure I've seen code that does that..
ah, yes, it's "Voldemort type". ;-)

  auto thatWhoCantBeNamed () {
    static struct A { ... }
    return A();
  }

voila. you have type that you can use but cannot create without factory. but you need to have postblit enabled with this.

> > you can pass pointers to structs. pointers can be null. ;-)
> I thought ref gives us the same guarantee that C++'s references do?  Pointers are so.. 90's =P  But yeah, the nullable object thing has bitten my ass a few times as I'm learning D, which really frustrates me =\
i've never used nullable myself. i'm just constantly forgetting about it (and about ~80% of Phobos for that matter ;-).

> Thanks for your time
your posts made me think about some things ;-), so thanks for your time too.


October 11, 2014
On Friday, 10 October 2014 at 09:58:54 UTC, Walter Bright wrote:
> On 11/27/2011 11:53 AM, deadalnix wrote:
>> I wonder why struct can't have a default constructor. TDPL state that it is
>> required to allow every types to have a constant .init .
>
> Having a .init instead of a default constructor has all kinds of useful properties:
>
> 1. the default object is trivially constructable and cannot fail
>
> 2. an easy way for other constructors to not have overlooked field initializers, so they get garbage initialized like in C++
>
> 3. generic code can rely on the existence of trivial construction that cannot fail
>
> 4. dummy objects can be created, useful for "does this compile" semantics
>
> 5. an object can be "destroyed" by overwriting it with .init (overwriting with 0 is not the same thing)
>
> 6. when you see:
>     T t;
> in code, you know it is trivially constructed and will succeed
>
> 7. objects can be created using TypeInfo
>
>
> Default constructors are baked into C++. I can't escape the impression that the desire for D default constructors comes from more or less trying to write C++ style code in D.
>
> I feel that non-trivial default construction is a bad idea, as are the various methods people try to get around the restriction. For non-trivial construction, one can easily just make a constructor with an argument, or call a factory method that returns a constructed object.

I've often thought that most of the hidden pain of arbitrary
constructors in C++ could be avoided if C++ could take advantage
of functional purity.

D has native functional purity.  Couldn't you get the same
benefits that you listed by allowing default constructors but
requiring them to be pure?  Of course, you can get the same
outcome by initialising members using static pure functions or
various other helpers, so you could say that pure default
constructors are just syntactic sugar.

Pure default constructors do have some advantages for more
complex construction, though.  For the sake of example, say I
have a struct that uses a table of complex numbers, and for
unrelated reasons the real and imaginary parts are stored in
separate arrays.  I.e., I have to initialise multiple members
using one calculation.  Adding pure default constructors to D
would allow this to be implemented more cleanly and more
intuitively.
October 11, 2014
On Friday, 10 October 2014 at 09:14:16 UTC, Marc Schütz wrote:
> You can use `static opCall` as a workaround.

Please don't. It is never worth the gain. Never.
October 11, 2014
On 10/10/2014 5:25 PM, Simon A wrote:
> D has native functional purity.  Couldn't you get the same
> benefits that you listed by allowing default constructors but
> requiring them to be pure?

I suspect that CTFE can accomplish most of that today - with the exception that CTFE will not allocate runtime memory for you.

October 11, 2014
On Saturday, 11 October 2014 at 04:41:52 UTC, Walter Bright wrote:
> On 10/10/2014 5:25 PM, Simon A wrote:
>> D has native functional purity.  Couldn't you get the same
>> benefits that you listed by allowing default constructors but
>> requiring them to be pure?
>
> I suspect that CTFE can accomplish most of that today - with the exception that CTFE will not allocate runtime memory for you.

There is ER somewhere in bugzilla AFAIR about allowing CTFE-only struct default constructors.
October 11, 2014
On 10/10/2014 9:43 PM, Dicebot wrote:
> On Saturday, 11 October 2014 at 04:41:52 UTC, Walter Bright wrote:
>> On 10/10/2014 5:25 PM, Simon A wrote:
>>> D has native functional purity.  Couldn't you get the same
>>> benefits that you listed by allowing default constructors but
>>> requiring them to be pure?
>>
>> I suspect that CTFE can accomplish most of that today - with the exception
>> that CTFE will not allocate runtime memory for you.
>
> There is ER somewhere in bugzilla AFAIR about allowing CTFE-only struct default
> constructors.

Note that you can do (as pointed out upthread):

   struct S {
        int x = 7;
        string s = "hello";
   }

which then has default initialization. Of course, CTFE will work on those rvalues.
October 11, 2014
On Friday, October 10, 2014 22:49:13 Walter Bright via Digitalmars-d wrote:
> On 10/10/2014 9:43 PM, Dicebot wrote:
> > There is ER somewhere in bugzilla AFAIR about allowing CTFE-only struct default constructors.
>
> Note that you can do (as pointed out upthread):
>
>     struct S {
>          int x = 7;
>          string s = "hello";
>     }
>
> which then has default initialization. Of course, CTFE will work on those rvalues.

The only reason that I can think of to have default constructors for filling in the member variables during CTFE would be if you wanted to calculate some of the values based on other values, and while that might be nice upon occasion, I don't think that not having it is much of a loss.

- Jonathan M Davis

October 11, 2014
On Friday, 10 October 2014 at 22:34:45 UTC, ketmar via
Digitalmars-d wrote:
>> I think I like the idea of the factory method now though, as I've learned you can hide a struct inside a function, and then call the function to set the struct up properly and return it. At least, I'm sure I've seen code that does that..
> ah, yes, it's "Voldemort type". ;-)
>
>   auto thatWhoCantBeNamed () {
>     static struct A { ... }
>     return A();
>   }
>
> voila. you have type that you can use but cannot create without
> factory. but you need to have postblit enabled with this.

It's not a "Voldemort type" if the struct A is static, is it? You
can just alias A = typeof(thatWhoCantBeNamed()); and then create
a new object of the same type.

--
Nicolas
October 11, 2014
On Friday, 10 October 2014 at 22:34:45 UTC, ketmar via Digitalmars-d wrote:
> On Fri, 10 Oct 2014 21:45:32 +0000
>> Shouldn't properties not mutate the data?
> it depends. ;-) i think that doing such "lazy initialization" in
> property is acceptable. maybe not the best style, but acceptable. yet i
> may be wrong, of course.

Ah, I see. Lazy initialization is a nice approach to some things. But I'm not entirely sure it belongs in the structure or object itself..  however, if I were too I might put it in a 'length' or 'rezise' member function.. but that'd be better for a resizable object..

>> better methods to doing it safely, for sure. But to do the same with only a single pointer?
> why do you insisting on having single pointer? sure you can use all
> sort of tricks to pack alot of data in single pointer, but i can't see
> any practical sense in it. today when smartphones have gigabytes of
> RAM, i'll trade some more pointers for ease of using and safety.

Actually, I think memory efficiency and speed are pretty key today, especially with embedded systems, server farms, and mobile devices. And it doesn't just have to do with available memory, but cache lines, register usage, etc.  But that's another debate for another time.

As far as the safety tradeoff though.. I prevent copying and default construction, so at least two problems should theoretically be solved. The possibility of using ranges or pointers outside of the function or scope is the only thing that would need to be controlled for.  And there I suppose the GC or reference counting mechanisms would be the only other options. But hopefully if I document it right, and suggest the correct alternatives to use, I can prevent careless programmers from making those mistakes. (I think std.container.array.Array is a nice fallback)

> ah, yes, it's "Voldemort type". ;-)

Ah, I had wondered why/where/what context I heard that under! Makes more sense now. =)

Also, thanks for the other pointers (I didnt quote everything here)
October 11, 2014
Okay, I'm still kind of new to this language, so forgive me if I see things a little differently than long time users..

Here's what I see though.

With Walter's description of what structures should be, it seems to me to be slightly at odds with what D structures are capable of, and in fact what they are being used for in a number of places ('fixing' GC, adding reference counting, providing RAII, etc).

Structs are classically POD types, as we know from C. But with D you've added overloaded constructors, destructors, member functions etc. To me that more or less defines an object, albeit one with special limits.  If you don't want to call them objects, thats fine. But the fact that they have customizable behavior in creation, copying, moving, and destruction means that they are much more than just a plain old data type with generic T.init states. They are a kind of living object, yea?

And it seems to me that the default constructor is just the last component left to cement the structure as an object. It seems curious that you would allow the ability to disable default construction and copy construction, and allow hiding of constructors, etc.. but then determine that default constructors are going too far?

Sure, having a guarantee that a struct will always be created without a fail state is a great concept.  But why then haven't they just been kept as a collection of data types, and another 'scope object' been created instead?

It seems what has happened is a FrankenStruct monster was created in the midst of trying to find some midway point between D objects and 'scope objects'..

I would myself love to use objects all the time, but I see these problems that currently only the FrankenStructs resolve:

1. Objects are basically pointers, and can be null pointers at that! (To me, that's C-levels of safety there)
   However: Structs are always created.
2. Without assigning something to an object, there's obviously no real initialization.
   However: Structs are initialized on creation.
3. Object Destructors are called 'sometime in the future' when GC runs. Cleaning up resources is discouraged as those resources might have been cleaned up by the GC?
   However: Struct destructors run when they are out of scope.
4. Objects always have at least one level of indirection (and a lot of extra data)
   Structs are there where you define them. (with no extra data)

I may not be 100% on all of this, I'm still learning. But many times browsing the forums, reading the D books etc, I see the solutions to D object problems becoming this: wrap it with a struct.  So whether its something like RefCounted, Scoped, NonNullable, UniqueArray etc.. the problems with D objects are fixed by relying on the guarantees provided by structures to solve them.

I dunno, to me it all seems a bit discordant with what Walter seems to want them to be?

(Sorry in advance if any of this comes off as mean or provoking.. I'm just sharing how I view the language design)