Jump to page: 1 2
Thread overview
Extending Primitive Types
Jun 15, 2005
Sean Kelly
Jun 15, 2005
James Dunne
Jun 15, 2005
Sean Kelly
Jun 16, 2005
pragma
Jun 16, 2005
Sean Kelly
Jun 17, 2005
Andrew Fedoniouk
Jun 17, 2005
Sean Kelly
Jun 17, 2005
Andrew Fedoniouk
Jun 17, 2005
Sean Kelly
Jun 17, 2005
Andrew Fedoniouk
Jun 17, 2005
Derek Parnell
Jun 17, 2005
Andrew Fedoniouk
Jun 17, 2005
Derek Parnell
Jun 17, 2005
Brad Beveridge
Jun 17, 2005
Andrew Fedoniouk
Jun 17, 2005
Derek Parnell
Jun 17, 2005
Andrew Fedoniouk
June 15, 2005
The lack of implicit type conversion in D is a good thing, but it can be annoying when attempting to implement objects that represent primitive types. I've been kicking around some ideas on how to add limited support for implicit conversion to D, and my first thought was this:

# class S : int
# {
#     int opConv() { return val; }
#     int val;
# }

Here we have a class that derives itself from int, and so by the rules of polymorphism, should be implicitly convertible to int (its parent type).  To aid in this conversion, I've suggested a new operator, opConv, that must return a value of the parent type.  This leaves us with two problems: first, using classes for fake primitives is annoying because they are dynamically allocated, and second, we still need a way to allow implicit assignments of the form:

# S val = 5;

In some respects, these problems are one and the same.  Consider this example:

# class P : int*
# {
#     // pretend opAssign exists
#     int* opAssign( int* v ) { val = v; }
#     int* opConv() { return val; }
#     int* val;
# }
#
# P val = new P();
# val = null; // ambiguous

Because D does not support pointer semantics for class types, an assignment must be assumed to operate on the reference and not the underlying value.  Adding an opAssign might be acceptable when assigning non-pointer values, but the above example shows that there are obvious problems with extending pointer types.  Are there any alternatives?  I suppose this feature could be limited to structs only, but it violates the basic premise that structs do not support polymorphism, though this does address the dynamic allocation problem quite nicely.  I'm left feeling that D should stay as it is and that this feature not be supported in any form, but that brings with it the impression that the syntax of D is unduly limiting.  Is it reasonable that a language which supports operator overloading has no facility for extending primitive types?  I'm hesitant to say yes.


Sean


June 15, 2005
*snip*
>Is it reasonable that a language which supports
>operator overloading has no facility for extending primitive types?  I'm
>hesitant to say yes.
>
>Sean
>

To clarify, the language only supports operator overloading within classes and structs.

It seems as if you are asking for something like a cross between a class and a struct, as they are defined in D.  If I read you correctly, this new thing should support inheritance (including from primitive types), support operator overloading, and be passed by value instead of reference.  Sounds like a job for a new aggregate 'usertype'.

Regards,
James Dunne
June 15, 2005
In article <d8ptv6$mk8$1@digitaldaemon.com>, James Dunne says...
>
>*snip*
>>Is it reasonable that a language which supports
>>operator overloading has no facility for extending primitive types?  I'm
>>hesitant to say yes.
>
>To clarify, the language only supports operator overloading within classes and structs.
>
>It seems as if you are asking for something like a cross between a class and a struct, as they are defined in D.  If I read you correctly, this new thing should support inheritance (including from primitive types), support operator overloading, and be passed by value instead of reference.  Sounds like a job for a new aggregate 'usertype'.

This is one of the few areas where I prefer C++ to D.  I'm not sure what the solution is, only that I find mandatry reference semantics for class types somewhat irritating.  There are plenty of times when I don't want to deal with DMA and using alloca() simply isn't feasible.  I suppose I could do something horrid like use placement new to construct a class in a static array, but I'd much rather the language had a facility for this.  Adding a new type is not a good solution IMO, as it doesn't seem substantially different from struct or class.


Sean


June 16, 2005
In article <d8psss$lrn$1@digitaldaemon.com>, Sean Kelly says...
>
>The lack of implicit type conversion in D is a good thing, but it can be annoying when attempting to implement objects that represent primitive types. I've been kicking around some ideas on how to add limited support for implicit conversion to D, and my first thought was this:
>
># class S : int
># {
>#     int opConv() { return val; }
>#     int val;
># }
>
>Here we have a class that derives itself from int, and so by the rules of polymorphism, should be implicitly convertible to int (its parent type).  To aid in this conversion, I've suggested a new operator, opConv, that must return a value of the parent type.  This leaves us with two problems: first, using classes for fake primitives is annoying because they are dynamically allocated, and second, we still need a way to allow implicit assignments of the form:
>
># S val = 5;
>
>In some respects, these problems are one and the same.  Consider this example:
>
># class P : int*
># {
>#     // pretend opAssign exists
>#     int* opAssign( int* v ) { val = v; }
>#     int* opConv() { return val; }
>#     int* val;
># }
>#
># P val = new P();
># val = null; // ambiguous
>
>Because D does not support pointer semantics for class types, an assignment must be assumed to operate on the reference and not the underlying value.  Adding an opAssign might be acceptable when assigning non-pointer values, but the above example shows that there are obvious problems with extending pointer types.  Are there any alternatives?  I suppose this feature could be limited to structs only, but it violates the basic premise that structs do not support polymorphism, though this does address the dynamic allocation problem quite nicely.  I'm left feeling that D should stay as it is and that this feature not be supported in any form, but that brings with it the impression that the syntax of D is unduly limiting.  Is it reasonable that a language which supports operator overloading has no facility for extending primitive types?  I'm hesitant to say yes.

Not to knock your idea, but I think part of the problem comes from attempting to mix object and value semantics into the same entity.  I would advocate that we give this facility to structs only and make conversion to the "inherited scalar type" implicit.  This way, things become far more clear to the compiler author and developer alike:

> struct Foobar : int{
> }
>
> Foobar x = 5; // implicit assignment to underlying type
> int y = x; // implict cast to underlying type


Also, I like the idea of adding an opConv() but I'm still partial to the idea of making opCast() more flexible instead.  I mentioned something like this many moons ago, but it met with a rather lukewarm response.

> class Gorf{
>    void* opCast(TypeInfo other); // flexible casting with zero covariance issues
> }

.. which is meant to mesh with the internal pobos cast functions, that DMD uses to support the cast() operator.  It also side-steps *all* of the current problems we have with opCast() as we currently have it.

IMO, A cast signature like the one above could also deal with some of the issues your looking to solve.

- EricAnderton at yahoo
June 16, 2005
In article <d8s0vv$27tg$1@digitaldaemon.com>, pragma says...
>
>Not to knock your idea, but I think part of the problem comes from attempting to mix object and value semantics into the same entity.  I would advocate that we give this facility to structs only and make conversion to the "inherited scalar type" implicit.  This way, things become far more clear to the compiler author and developer alike:

I agree, except I think doing so violates Walter's concept of what structs should be in D.  Frankly, I'm surprised we can add member functions and operator overloads to structs (but no ctor--I'll never understand that one).  I suspect it's my background with C++, but I don't understand why classes aren't allowed value semantics.  I kind of wish this lack of consistency were removed and we could do either:

# MyClass c1;
# MyClass* c2 = new MyClass();

Pointer types would be garbage collected and value types would be allocated on the stack.  This isn't Java where pointers don't exist as a semantic construct, so why do we pretend this is so for classes?

>Also, I like the idea of adding an opConv() but I'm still partial to the idea of making opCast() more flexible instead.  I mentioned something like this many moons ago, but it met with a rather lukewarm response.

I'm undecided on this.  opCast does already do basically what we want, but I thought it might be easier on the compiler if it weren't also used for implicit conversion, since the latter is almost a special case.

>> class Gorf{
>>    void* opCast(TypeInfo other); // flexible casting with zero covariance issues
>> }
>
>.. which is meant to mesh with the internal pobos cast functions, that DMD uses to support the cast() operator.  It also side-steps *all* of the current problems we have with opCast() as we currently have it.

True enough.  Though I'm not sure I like that this moves detecting whether something is convertible from compile to run-time.  I would almost prefer template specializations:

# class MyClass {
#   template opCast(T:int) { int opCast() { return 1; } }
#   template opCast(T:float) { float opCast() { return 1.0; } }
# }

Except this is kind of weird to look at.


Sean


June 17, 2005

"Sean Kelly" <sean@f4.ca> wrote in message news:d8psss$lrn$1@digitaldaemon.com...
> The lack of implicit type conversion in D is a good thing, but it can be
> annoying when attempting to implement objects that represent primitive
> types.
> I've been kicking around some ideas on how to add limited support for
> implicit
> conversion to D, and my first thought was this:
>
> # class S : int
> # {
> #     int opConv() { return val; }
> #     int val;
> # }
>
> Here we have a class that derives itself from int, and so by the rules of
> polymorphism, should be implicitly convertible to int (its parent type).
> To aid
> in this conversion, I've suggested a new operator, opConv, that must
> return a
> value of the parent type.  This leaves us with two problems: first, using
> classes for fake primitives is annoying because they are dynamically
> allocated,
> and second, we still need a way to allow implicit assignments of the form:
>
> # S val = 5;
>
> In some respects, these problems are one and the same.  Consider this example:
>
> # class P : int*
> # {
> #     // pretend opAssign exists
> #     int* opAssign( int* v ) { val = v; }
> #     int* opConv() { return val; }
> #     int* val;
> # }
> #
> # P val = new P();
> # val = null; // ambiguous
>
> Because D does not support pointer semantics for class types, an
> assignment must
> be assumed to operate on the reference and not the underlying value.
> Adding an
> opAssign might be acceptable when assigning non-pointer values, but the
> above
> example shows that there are obvious problems with extending pointer
> types.  Are
> there any alternatives?  I suppose this feature could be limited to
> structs
> only, but it violates the basic premise that structs do not support
> polymorphism, though this does address the dynamic allocation problem
> quite
> nicely.  I'm left feeling that D should stay as it is and that this
> feature not
> be supported in any form, but that brings with it the impression that the
> syntax
> of D is unduly limiting.  Is it reasonable that a language which supports
> operator overloading has no facility for extending primitive types?  I'm
> hesitant to say yes.
>

Sean, I think it is better to extend struct for that
as struct is also primitive type and has copy semantic already

Definition by example:

struct string: wchar[]
{
    ....
    void insert ( uint where, wchar[] what );
    void remove ( uint where, uint length = 1 );
    ....
};

Ideally struct should have
opAssign, opSliceAssign,
ctors and dtors.
If latter two will be available then 'auto' keyword can be removed.
'auto' is too artificial and looks like a language design patch.
As a rule auto objects must be as lightweight as possible and
class instances are far not the best solution from that point of view.

Availability of opAssign, opSliceAssign will allow to implement
ownership and transfer ownership patterns.
E.g. implementation of smart pointers now
is just physically impossible in D. I am not speaking here of are
they good or bad. Just about design pattern and technic widely
used in C++.

Question about general motivational principle: Shall D allow to implement basic code patterns available in C++ world or not?

If D is more targeted on to be a Java replacement (which is also fine) then the same question is still valid but about Java.

Clear answer on these questions will help in many areas.

Andrew.





























June 17, 2005
In article <d8trqd$l3e$1@digitaldaemon.com>, Andrew Fedoniouk says...
>
>Question about general motivational principle: Shall D allow to implement basic code patterns available in C++ world or not?

that's a good question.  I've been assuming the answer is "yes" but I suppose I should not.  In some respects, D feels like it's the Java class system grafted onto a C-like core language.  Each aspect is quite elegant and easy to use in isolation, but I haven't been able to resolve some of the contraints of class types with respect to the language as a whole.  If the difference between structs and classes is truly that structs are value types and classes are reference types, then structs need ctors and opAssign.  If not, then I'd argue structs should be treated as aggregate types and perhaps have operator overloading stripped from them entirely.  And then classes could either stay as-is or they could allow for value semantics as approprate (ideally, the latter, as I believe full value type support is important).

I admit that this isn't horrible to work around now... it just feels awkward. For example, I can do this:

# struct V {
#     int val() { return v; }
#     int val( int p ) { return v = p; }
#     int v;
#     // operators here
# }
#
# v myV;
# int i = 1 + myV.val;
# myV.val = i;
# myV += 2;

But needing to use a property method in some cases and not in others is just jarring.

Sean


June 17, 2005
"Sean Kelly" <sean@f4.ca> wrote in message news:d8uon2$1g1a$1@digitaldaemon.com...
> In article <d8trqd$l3e$1@digitaldaemon.com>, Andrew Fedoniouk says...
>>
>>Question about general motivational principle: Shall D allow to implement basic code patterns available in C++ world or not?
>
> that's a good question.  I've been assuming the answer is "yes" but I
> suppose I
> should not.  In some respects, D feels like it's the Java class system
> grafted
> onto a C-like core language.  Each aspect is quite elegant and easy to use
> in
> isolation, but I haven't been able to resolve some of the contraints of
> class
> types with respect to the language as a whole.

Well formulated, Sean!
I've got the same impression.
Harmonia design experience:
D suits well for single person design in limited timeframe.
But for working in teams, there is a lack of incapsulation features.
Please see below.

> If the difference between
> structs and classes is truly that structs are value types and classes are
> reference types, then structs need ctors and opAssign.  If not, then I'd
> argue
> structs should be treated as aggregate types and perhaps have operator
> overloading stripped from them entirely.  And then classes could either
> stay
> as-is or they could allow for value semantics as approprate (ideally, the
> latter, as I believe full value type support is important).
>
> I admit that this isn't horrible to work around now... it just feels
> awkward.
> For example, I can do this:
>
> # struct V {
> #     int val() { return v; }
> #     int val( int p ) { return v = p; }
> #     int v;
> #     // operators here
> # }
> #
> # v myV;
> # int i = 1 + myV.val;
> # myV.val = i;
> # myV += 2;
>
> But needing to use a property method in some cases and not in others is
> just
> jarring.

Good example too. It means that you cannot implement in D atomic (for some
domain) and
encapsulated types:

# myV.val = i;  // structure
# myV += 2;   // and here it pretends to be an atomic value.

User of your myV must alway keep in mind when it is a structure and when it
is a value.
All "private parts" *must be* (in D)
exposed to outer world. This is a logical conflict - at the same
time D provides private, protected, package attributes and at the same time
it does not allow you to use them in practice in some but pretty critical
cases.

opAssign and co. in C++ allow to implement controlled ownership of memory
area.
But in D it is just impossible.
Try to catch situation in D:

myV = myAnotherV.

Just no natural way nor workarounds for this in D at all.

This means again that the whole class of RAII cases is just gone - no smart pointers or the like in D. And this is huge and highly usable area in modern C++.

'auto' does not help here, see:

auto myV = new V;   // #1 instance
        myV = new V;  // #2 instance, doh!

It is not a RAII strictly speaking. Just a fragment of RAII cases. (I am yet silent that it forces allocation on the heap)

Let's take a look then from "D is C (not C++) with  Java classes" point of
view.
In fact D is farther from Java than from C++ in magnitude of degree.
Java is all about pointer safety. The sole and highly usable (in C/C++)
feature [0..N] throws D out of Java world entirely. Java concepts and code
simply cannot be applicable in D. Java String cannot be reproduced in D in
principle:
String "hello world" in Java is an instance of class String which is
immutable atomic type.
And Java code heavily relies on this fact.

Andrew.



June 17, 2005
In article <d8v7hh$1ssv$1@digitaldaemon.com>, Andrew Fedoniouk says...
>
>opAssign and co. in C++ allow to implement controlled ownership of memory
>area.
>But in D it is just impossible.
>Try to catch situation in D:
>
>myV = myAnotherV.

Ouch!  I hadn't thought of this.  I'm currently implementing atomic values in D, and this totally blows the design out of the water, as it effectively alters an atomic value without using the proper memory barriers.  I may have to switch from using 'struct' to 'class' just to avoid this problem, and that brings with it all the DMA baggage I was hoping to avoid.

>This means again that the whole class of RAII cases is just gone - no smart pointers or the like in D. And this is huge and highly usable area in modern C++.

To be truly invisible, a smart pointer in D (or an iterator, or anything pretending to be something else) would need to overload operator '.', which I don't see Walter allowing any time soon.  Though I'm willing to live with using a 'val' property method for accessing the underlying data.

>'auto' does not help here, see:
>
>auto myV = new V;   // #1 instance
>        myV = new V;  // #2 instance, doh!
>
>It is not a RAII strictly speaking. Just a fragment of RAII cases. (I am yet silent that it forces allocation on the heap)

Good observation--the second assignment leaves instance #1 to wait for the next GC cycle to be collected, doesn't it?  What about this?

# auto myVa = new V;
# auto myVb = myVa;

I assume this would cause an access violation or something equally horrible? I'll admit I haven't played with auto classes much yet, so I don't know how good the compiler is about preventing their misuse.

>Let's take a look then from "D is C (not C++) with  Java classes" point of
>view.
>In fact D is farther from Java than from C++ in magnitude of degree.
>Java is all about pointer safety. The sole and highly usable (in C/C++)
>feature [0..N] throws D out of Java world entirely. Java concepts and code
>simply cannot be applicable in D. Java String cannot be reproduced in D in
>principle:
>String "hello world" in Java is an instance of class String which is
>immutable atomic type.
>And Java code heavily relies on this fact.

I do see D to be like Java in that classes can only be used by reference, and references aren't really pointers.  This is fine in itself, but I find it weird in a language that actually *has* pointers.  That aside, you're right that without the ability to define opAssign or a copy ctor, it may be impossible to define a String class in D that behaves like the String class in Java.  I imagine any code conversion would have to add .dup all over the place just to insure correctness.  Kris may actually be a good person to ask about this, as he's been looking at this issue rather closely--could a COW String class be implemented in D that would be sufficient for this purpose?  And is one even needed?  I'll have to give it some thought.


Sean


June 17, 2005
"Sean Kelly" <sean@f4.ca> wrote in message news:d8vasu$1vdg$1@digitaldaemon.com...
> In article <d8v7hh$1ssv$1@digitaldaemon.com>, Andrew Fedoniouk says...
>>
>>opAssign and co. in C++ allow to implement controlled ownership of memory
>>area.
>>But in D it is just impossible.
>>Try to catch situation in D:
>>
>>myV = myAnotherV.
>
> Ouch!  I hadn't thought of this.  I'm currently implementing atomic values
> in D,
> and this totally blows the design out of the water, as it effectively
> alters an
> atomic value without using the proper memory barriers.  I may have to
> switch
> from using 'struct' to 'class' just to avoid this problem, and that brings
> with
> it all the DMA baggage I was hoping to avoid.

classes are not allowing to create memory bariers too.

About iterators:
"Politically correct" and effective implementation of modifying iterators
shall assume that there is a mechanism of reference counting
available in the language.

Container (observable) shall maintain number of currently existing
iterators (observers). If this number is greater than one then any
modifying operation shall throw an exception.
Only in this case it is possible to build really safe (thread safe is also
here) system of container/iterators. This is what theory says.

Without opAssign and ctors/dtors reliable implementation of such iterators is impossible in D.

>
>>This means again that the whole class of RAII cases is just gone - no smart pointers or the like in D. And this is huge and highly usable area in modern C++.
>
> To be truly invisible, a smart pointer in D (or an iterator, or anything
> pretending to be something else) would need to overload operator '.',
> which I
> don't see Walter allowing any time soon.  Though I'm willing to live with
> using
> a 'val' property method for accessing the underlying data.

imho, smart pointers are not only about overloading '.' or '->'. It is a
nice
feature ('pointer' part) but not main. smart pointers are *owners* of
references
('smart' part). E.g. they allow to use exception mechanism in full (dtors).

>
>>'auto' does not help here, see:
>>
>>auto myV = new V;   // #1 instance
>>        myV = new V;  // #2 instance, doh!
>>
>>It is not a RAII strictly speaking. Just a fragment of RAII cases. (I am yet silent that it forces allocation on the heap)
>
> Good observation--the second assignment leaves instance #1 to wait for the
> next
> GC cycle to be collected, doesn't it?  What about this?
>
> # auto myVa = new V;
> # auto myVb = myVa;
>
> I assume this would cause an access violation or something equally
> horrible?
> I'll admit I haven't played with auto classes much yet, so I don't know
> how good
> the compiler is about preventing their misuse.
>
>>Let's take a look then from "D is C (not C++) with  Java classes" point of
>>view.
>>In fact D is farther from Java than from C++ in magnitude of degree.
>>Java is all about pointer safety. The sole and highly usable (in C/C++)
>>feature [0..N] throws D out of Java world entirely. Java concepts and code
>>simply cannot be applicable in D. Java String cannot be reproduced in D in
>>principle:
>>String "hello world" in Java is an instance of class String which is
>>immutable atomic type.
>>And Java code heavily relies on this fact.
>
> I do see D to be like Java in that classes can only be used by reference,
> and
> references aren't really pointers.  This is fine in itself, but I find it
> weird
> in a language that actually *has* pointers.  That aside, you're right that
> without the ability to define opAssign or a copy ctor, it may be
> impossible to
> define a String class in D that behaves like the String class in Java.  I
> imagine any code conversion would have to add .dup all over the place just
> to
> insure correctness.  Kris may actually be a good person to ask about this,
> as
> he's been looking at this issue rather closely--could a COW String class
> be
> implemented in D that would be sufficient for this purpose?  And is one
> even
> needed?  I'll have to give it some thought.

Implementation of COW String class is not feasible in D now.
To be precise it is possible but you will give up slices and other nice
features making D fast and lightweight - this will follow to another
Java/.NET.

In most cases it is enough to have const string abstraction which is again demands either 'const' or opAssign/opSliceAssign. Deadlock.

Andrew.


« First   ‹ Prev
1 2