www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Why D needs implicit instantion or an autotype keyword

reply "Matthew" <admin stlsoft.dot.dot.dot.dot.org> writes:
Ok, some of you may have followed a recent post describing composition of
filtering operations on DTL containers, e.g.


    foreach(X x;
cont.select!(SomePredicate).collect(SomeTxFunction).select(SomeDelegate))
    {
      . . .
    }

Although the compiler currently eats my machine when trying to compile this, I'm
pretty confident that it can be achieved soon without much hassles. This all
works reasonably neatly because all <range> objects (those returned by
transformations on containers or other <ranges>) support foreach. So far so
good.

There'll also be support for getting a specific item out of container/range, via
methods like max(), min_if(), find_first(), as in:

    Person m =
cont.select!(SomePredicate).collect(SomeTxFunction).max(SomeDelegate)

That's also quite straightforward.

However, here's the rub. If you want to get hold of a range and save it for
later
processing, you need to know its type. The problem is that its type can be
something incredibly complex.

Consider the following fictional case, where we're filtering the contents of a
List of Person

container:                                                  List!(Person)
results of select() (with delegate IsMan):
MatchedNotionalRange!(_ListUtil!(Person).ListRange, DelegatePredicate!(bool
delegate(in Person value), Person))

Now that's just the one level. Imagine what it looks like when we're composing
another filter on top of the results of select()!

In C++ one can obviate such things by writing working template functions. Since
C++ supports implicit instantiation, the worker function can deduce such
horrendous mouthfuls for us. Of course, this is not always appropriate, but it
does help a lot.

Since D is not going to have implicit instantiation, I suggest that we *must*
have the autotype keyword, which will be akin to the semantics proposed for auto
in C++ 0.x. That is to say, the type is deduced, at compile time - it's no
variant type! - from the initialising expression, so:

    bool IsOdd(Number n);
    Number Square(Number n);

    autotype r = cont.select!(IsOdd).collect(Square)

    . . .

    for(; r.open; r.advance())
    {
        writeln("Number: ", r.current);
    }

AFAIUI, there's no major conceptual difficulty in doing this, since the compiler
already knows the type of the initialising expression. The only area of
uncertainty is that we might lose the polymorphic nature of the type, but I'd
say
that if you know what type it should be, then you don't need autotype. Kind of
axiomatic, but maybe I've missed something

Thoughts, everyone? Walter?!?
Jul 18 2004
next sibling parent "Ivan Senji" <ivan.senji public.srce.hr> writes:
"Matthew" <admin stlsoft.dot.dot.dot.dot.org> wrote in message
news:cddhd8$275p$2 digitaldaemon.com...
 Ok, some of you may have followed a recent post describing composition of
 filtering operations on DTL containers, e.g.


     foreach(X x;
 cont.select!(SomePredicate).collect(SomeTxFunction).select(SomeDelegate))
     {
       . . .
     }

 Although the compiler currently eats my machine when trying to compile

 pretty confident that it can be achieved soon without much hassles. This

 works reasonably neatly because all <range> objects (those returned by
 transformations on containers or other <ranges>) support foreach. So far

 There'll also be support for getting a specific item out of

 methods like max(), min_if(), find_first(), as in:

     Person m =
 cont.select!(SomePredicate).collect(SomeTxFunction).max(SomeDelegate)

 That's also quite straightforward.

 However, here's the rub. If you want to get hold of a range and save it

 processing, you need to know its type. The problem is that its type can be
 something incredibly complex.

 Consider the following fictional case, where we're filtering the contents

 List of Person

 container:                                                  List!(Person)
 results of select() (with delegate IsMan):
 MatchedNotionalRange!(_ListUtil!(Person).ListRange,

 delegate(in Person value), Person))

 Now that's just the one level. Imagine what it looks like when we're

 another filter on top of the results of select()!

 In C++ one can obviate such things by writing working template functions.

 C++ supports implicit instantiation, the worker function can deduce such
 horrendous mouthfuls for us. Of course, this is not always appropriate,

 does help a lot.

 Since D is not going to have implicit instantiation, I suggest that we

 have the autotype keyword, which will be akin to the semantics proposed

 in C++ 0.x. That is to say, the type is deduced, at compile time - it's no
 variant type! - from the initialising expression, so:

     bool IsOdd(Number n);
     Number Square(Number n);

     autotype r = cont.select!(IsOdd).collect(Square)

     . . .

     for(; r.open; r.advance())
     {
         writeln("Number: ", r.current);
     }

 AFAIUI, there's no major conceptual difficulty in doing this, since the

 already knows the type of the initialising expression. The only area of
 uncertainty is that we might lose the polymorphic nature of the type, but

 that if you know what type it should be, then you don't need autotype.

 axiomatic, but maybe I've missed something

 Thoughts, everyone? Walter?!?

I like it! I also liked that proposal for C++, so why not for D? :)
Jul 18 2004
prev sibling next sibling parent reply Matthias Becker <Matthias_member pathlink.com> writes:
Thoughts, everyone? Walter?!?

Well, I proposed this some time ago, got only good response, but Walter didn't answer. If you look at Walters style you can understand this. He declares all variables on top of his functions, like Pascal coders do or C programmers before 1999. Anyway, I don't like the name autotype. In C++0x auto is used so they don't have to introduce a new keyword. But auto is already used in D (well it is in C++, too, but with another meaning, that can't conflict in the situations, where you need the new auto). Other languages use let, var or val for this. Why don't we use one of these words? -- Matthias Becker
Jul 18 2004
parent reply "Matthew Wilson" <admin.hat stlsoft.dot.org> writes:
"Matthias Becker" <Matthias_member pathlink.com> wrote in message
news:cddv6v$2bi8$1 digitaldaemon.com...
Thoughts, everyone? Walter?!?

Well, I proposed this some time ago, got only good response, but Walter

 answer.

 If you look at Walters style you can understand this. He declares all

 on top of his functions, like Pascal coders do or C programmers before

 Anyway, I don't like the name autotype. In C++0x auto is used so they

 to introduce a new keyword. But auto is already used in D (well it is in

 too, but with another meaning, that can't conflict in the situations,

 need the new auto).

 Other languages use let, var or val for this. Why don't we use one of

 words?

I don't really care about the name, although I don't like let and val, and I assume var will be reserved for a built-in variant type in the future.
Jul 18 2004
parent "Bent Rasmussen" <exo bent-rasmussen.info> writes:
 I don't really care about the name, although I don't like let and val, and

 assume var will be reserved for a built-in variant type in the future.

I like "val", it reminds me of ML.
Jul 18 2004
prev sibling next sibling parent reply Norbert Nemec <Norbert.Nemec gmx.de> writes:
You are talking about two connected but distinct issues here: implicit
instantiation and automatic type deduction for local variables.

The first is dearly needed for template metaprogramming, and especially for
expression templates. It is far more than just a convenience issue. A
nested expression of moderate complexity, will usually have a type of
nested templates of the same depth. Furthermore: the exact type of the
expression may not even be easy to deduct, but it may follow from some
lengthy compile-time computation.

With types of local variables, the situation is similar. Of course, you
could in principle always deduct them and give them explicitly, but for a
non-trivial meta-program, this deduction may be arbitrarily complicated.
The compiler, of course, can always deduct it (that's why it is called
compile-time-computation).

B.t.w: autotypes for local variables have proven rather convenient in Sather
as well. There is no meta-programming possibility, so it really is just a
matter of convenience, but still, you quickly get addicted to it...




Matthew wrote:

 Ok, some of you may have followed a recent post describing composition of
 filtering operations on DTL containers, e.g.
 
 
     foreach(X x;
 cont.select!(SomePredicate).collect(SomeTxFunction).select(SomeDelegate))
     {
       . . .
     }
 
 Although the compiler currently eats my machine when trying to compile
 this, I'm pretty confident that it can be achieved soon without much
 hassles. This all works reasonably neatly because all <range> objects
 (those returned by transformations on containers or other <ranges>)
 support foreach. So far so good.
 
 There'll also be support for getting a specific item out of
 container/range, via methods like max(), min_if(), find_first(), as in:
 
     Person m =
 cont.select!(SomePredicate).collect(SomeTxFunction).max(SomeDelegate)
 
 That's also quite straightforward.
 
 However, here's the rub. If you want to get hold of a range and save it
 for later processing, you need to know its type. The problem is that its
 type can be something incredibly complex.
 
 Consider the following fictional case, where we're filtering the contents
 of a List of Person
 
 container:                                                  List!(Person)
 results of select() (with delegate IsMan):
 MatchedNotionalRange!(_ListUtil!(Person).ListRange,
 DelegatePredicate!(bool delegate(in Person value), Person))
 
 Now that's just the one level. Imagine what it looks like when we're
 composing another filter on top of the results of select()!
 
 In C++ one can obviate such things by writing working template functions.
 Since C++ supports implicit instantiation, the worker function can deduce
 such horrendous mouthfuls for us. Of course, this is not always
 appropriate, but it does help a lot.
 
 Since D is not going to have implicit instantiation, I suggest that we
 *must* have the autotype keyword, which will be akin to the semantics
 proposed for auto in C++ 0.x. That is to say, the type is deduced, at
 compile time - it's no variant type! - from the initialising expression,
 so:
 
     bool IsOdd(Number n);
     Number Square(Number n);
 
     autotype r = cont.select!(IsOdd).collect(Square)
 
     . . .
 
     for(; r.open; r.advance())
     {
         writeln("Number: ", r.current);
     }
 
 AFAIUI, there's no major conceptual difficulty in doing this, since the
 compiler already knows the type of the initialising expression. The only
 area of uncertainty is that we might lose the polymorphic nature of the
 type, but I'd say that if you know what type it should be, then you don't
 need autotype. Kind of axiomatic, but maybe I've missed something
 
 Thoughts, everyone? Walter?!?

Jul 18 2004
parent reply "Bent Rasmussen" <exo bent-rasmussen.info> writes:
 B.t.w: autotypes for local variables have proven rather convenient in

 as well. There is no meta-programming possibility, so it really is just a
 matter of convenience, but still, you quickly get addicted to it...

It sure would be sweet to have both in 2.0. I like it even before I've tried it. :-) val t, i; ... t = s[5 .. 25]; i = index(s,"abc"); begone bloat
Jul 18 2004
parent reply Norbert Nemec <Norbert.Nemec gmx.de> writes:
Bent Rasmussen wrote:

 B.t.w: autotypes for local variables have proven rather convenient in

 as well. There is no meta-programming possibility, so it really is just a
 matter of convenience, but still, you quickly get addicted to it...

It sure would be sweet to have both in 2.0. I like it even before I've tried it. :-) val t, i; ... t = s[5 .. 25]; i = index(s,"abc");

I think, the feature only makes sense for variables that are immediately initialized: val t = s[5 .. 25]; val i = index(s,"abc"); since the initialization is needed to determine the type. Of course, it would be possible to allow predeclaring such variables, but then you would have to assure that the variable is initialized at exactly one point, and it would seem strange that the first assignment to the variable has a special meaning compared to the others.
Jul 19 2004
next sibling parent "Bent Rasmussen" <exo bent-rasmussen.info> writes:
 since the initialization is needed to determine the type. Of course, it
 would be possible to allow predeclaring such variables, but then you would
 have to assure that the variable is initialized at exactly one point, and
 it would seem strange that the first assignment to the variable has a
 special meaning compared to the others.

I don't find it that confusing*, in fact an error message would clear up any confusion quite easily: "(23): attempt to redefine type of 't' from prior definition (15)" or something like that. It is a choice to be made by the programmer. Its one more "use it if you like it, don't if you don't" kind of thing. But of course if it turns out confusing to too many then its probably not a good idea. It would be interesting to have an editor with sufficient compiler interaction to annotate the types of values as you assign expressions to them. * But then there's no experience with it to speak of.
Jul 20 2004
prev sibling parent Andy Friesen <andy ikagames.com> writes:
Norbert Nemec wrote:
 Bent Rasmussen wrote:
 
 
B.t.w: autotypes for local variables have proven rather convenient in

Sather
as well. There is no meta-programming possibility, so it really is just a
matter of convenience, but still, you quickly get addicted to it...

It sure would be sweet to have both in 2.0. I like it even before I've tried it. :-) val t, i; ... t = s[5 .. 25]; i = index(s,"abc");

I think, the feature only makes sense for variables that are immediately initialized: val t = s[5 .. 25]; val i = index(s,"abc"); since the initialization is needed to determine the type. Of course, it would be possible to allow predeclaring such variables, but then you would have to assure that the variable is initialized at exactly one point, and it would seem strange that the first assignment to the variable has a special meaning compared to the others.

Right: "If it's easy to explain, it might be a good idea." (quoth the zen of Python) - Can be used for local variables and constants only. - Must be initialized in its declaration. The attribute's type is statically determined by the initializer. - Otherwise not special in any way. (no implicit constness, runtime variant behaviour or anything like that) Also, this would offer a simple way to get an integer whose size is whatever the host CPU likes best: var x = 0; -- andy
Jul 20 2004
prev sibling next sibling parent reply Andy Friesen <andy ikagames.com> writes:
Matthew wrote:
 Since D is not going to have implicit instantiation, I suggest that we *must*
 have the autotype keyword, which will be akin to the semantics proposed for
auto
 in C++ 0.x. That is to say, the type is deduced, at compile time - it's no
 variant type! - from the initialising expression, so:
 
 Thoughts, everyone? Walter?!?

It even looks (to my feeble brain, at least) easy to implement: autotype r = expr; // equivalent to: typeof(expr) r = expr; -- andy
Jul 18 2004
next sibling parent reply "Carlos Santander B." <carlos8294 msn.com> writes:
"Andy Friesen" <andy ikagames.com> escribió en el mensaje
news:cde51k$2dpc$1 digitaldaemon.com
| Matthew wrote:
|| Since D is not going to have implicit instantiation, I suggest that we
*must*
|| have the autotype keyword, which will be akin to the semantics proposed
for auto
|| in C++ 0.x. That is to say, the type is deduced, at compile time - it's
no
|| variant type! - from the initialising expression, so:
||
|| Thoughts, everyone? Walter?!?
|

I don't really care about the name, but I do like the idea.

| It even looks (to my feeble brain, at least) easy to implement:
|
| autotype r = expr;
| // equivalent to:
| typeof(expr) r = expr;
|
|   -- andy

What if "expr" is really complex (like that really long expression that
Matthew posted before) and the compiler can't directly determine what it's
type is? Wouldn't it mean evaluating it twice? Of course, if it just can't
happen, then just tell me so, I just wanna be sure.

-----------------------
Carlos Santander Bernal
Jul 18 2004
next sibling parent reply "Bent Rasmussen" <exo bent-rasmussen.info> writes:
 What if "expr" is really complex (like that really long expression that
 Matthew posted before) and the compiler can't directly determine what it's
 type is? Wouldn't it mean evaluating it twice? Of course, if it just can't
 happen, then just tell me so, I just wanna be sure.

I can't imagine. To test this write type value = expr; where typeof(expr) is incompatible with type, and expr is an arbitrarily complex expression. It should be clear that since the compiler can catch type mismatches between the type of value and the type of expr, it must know the type of expr and hence be able to deduce the type of a val/autotype. I can imagine implicit template instantiation complex to implement though. But then either it can deduce the types or it can't, in which case compilation fails.
Jul 18 2004
parent "Carlos Santander B." <carlos8294 msn.com> writes:
"Bent Rasmussen" <exo bent-rasmussen.info> escribió en el mensaje
news:cdf91l$2sjf$1 digitaldaemon.com
| I can't imagine. To test this write
|
|     type value = expr;
|
| where typeof(expr) is incompatible with type, and expr is an arbitrarily
| complex expression.
|
| It should be clear that since the compiler can catch type mismatches
between
| the type of value and the type of expr, it must know the type of expr and
| hence be able to deduce the type of a val/autotype.
|
| I can imagine implicit template instantiation complex to implement though.
| But then either it can deduce the types or it can't, in which case
| compilation fails.

Yes, I think that makes sense.

-----------------------
Carlos Santander Bernal
Jul 18 2004
prev sibling parent Norbert Nemec <Norbert.Nemec gmx.de> writes:
Carlos Santander B. wrote:

 "Andy Friesen" <andy ikagames.com> escribió en el mensaje
 news:cde51k$2dpc$1 digitaldaemon.com
 | Matthew wrote:
 || Since D is not going to have implicit instantiation, I suggest that we
 *must*
 || have the autotype keyword, which will be akin to the semantics proposed
 for auto
 || in C++ 0.x. That is to say, the type is deduced, at compile time - it's
 no
 || variant type! - from the initialising expression, so:
 ||
 || Thoughts, everyone? Walter?!?
 |
 
 I don't really care about the name, but I do like the idea.
 
 | It even looks (to my feeble brain, at least) easy to implement:
 |
 | autotype r = expr;
 | // equivalent to:
 | typeof(expr) r = expr;
 |
 |   -- andy
 
 What if "expr" is really complex (like that really long expression that
 Matthew posted before) and the compiler can't directly determine what it's
 type is? Wouldn't it mean evaluating it twice? Of course, if it just can't
 happen, then just tell me so, I just wanna be sure.

In general, the compiler can always determine the type of an exception without knowledge of its usage. There are just two exceptions: * function pointers/delegates to overloaded functions. The syntax here is not sufficient to determine which of the functions is meant, so the compiler has to look at the variable that it is assigned to. * struct/array initializers. Even though these look like plain assignments at first sight, they are not. In both cases, type deduction would not work. In all other cases, it is no problem.
Jul 19 2004
prev sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
I agree that this would be a good and useful feature for 2.0.  In the 
meantime, it would be a very easy thing to implement with a 
preprocessing tool.

Andy Friesen wrote:
 It even looks (to my feeble brain, at least) easy to implement:
 
     autotype r = expr;
     // equivalent to:
     typeof(expr) r = expr;
 
  -- andy

Jul 19 2004
parent reply Andy Friesen <andy ikagames.com> writes:
Russ Lewis wrote:

 Andy Friesen wrote:
 
 It even looks (to my feeble brain, at least) easy to implement:

     autotype r = expr;
     // equivalent to:
     typeof(expr) r = expr;

  -- andy

I agree that this would be a good and useful feature for 2.0. In the meantime, it would be a very easy thing to implement with a preprocessing tool.

Maybe, but what I was trying to imply was that it's so easy to implement that it could realistically be added before 1.0. (the weight of the world hasn't crushed my hopes yet!) -- andy
Jul 19 2004
parent Matthias Becker <Matthias_member pathlink.com> writes:
 I agree that this would be a good and useful feature for 2.0.  In the 
 meantime, it would be a very easy thing to implement with a 
 preprocessing tool.
 

Maybe, but what I was trying to imply was that it's so easy to implement that it could realistically be added before 1.0. (the weight of the world hasn't crushed my hopes yet!)

Hmm, I proposed it about a half year ago and we still don't have it. Anyway, are we going to allow something like this: void foo (autotype bar) {...} So we would have the missed implicitly instantiated functions. -- Matthias Becker
Jul 20 2004
prev sibling next sibling parent reply nail <nail_member pathlink.com> writes:
What about multimethods? It would be fine if I can do following:

class Figure { ... }
class Rect : Figure { ... }
class Circle: Figure { ... }
class Triangle: Figure { ... }

float IntersectionSquare(Rect r, Circle c) // (1)
{ ... }

float IntersectionSquare(Circle r, Circle c) // (2)
{ ... }

float IntersectionSquare(Rect r, Figure f) // (3)
{ ... }

multimethod float IntersectionSquare(Figure a, Figure b);

void main()
{
Figure r = new Rect;
Figure c = new Circle;
Figure t = new Triangle;
float sq;

sq = IntersectionSquare(r, c); // (1) called
sq = IntersectionSquare(c, c); // (2) called
sq = IntersectionSquare(r, t); // (3) called
}

Any facts against?
Jul 20 2004
next sibling parent reply Sean Kelly <sean f4.ca> writes:
Why the new keyword?  Doesn't D handle overload resolution just fine without it?


Sean
Jul 20 2004
parent reply Matthias Becker <Matthias_member pathlink.com> writes:
In article <cdjekg$1ki0$1 digitaldaemon.com>, Sean Kelly says...
Why the new keyword?  Doesn't D handle overload resolution just fine without it?

Nope. There is no overload resolution at runtime. And that's what multimethods are all about.
Jul 21 2004
parent Sean Kelly <sean f4.ca> writes:
In article <cdlc1g$2g7g$1 digitaldaemon.com>, Matthias Becker says...
In article <cdjekg$1ki0$1 digitaldaemon.com>, Sean Kelly says...
Why the new keyword?  Doesn't D handle overload resolution just fine without it?

Nope. There is no overload resolution at runtime. And that's what multimethods are all about.

Please excuse me while I kick myself :) Thanks for the clarification. BTW, I agree that multimethods would be a great feature to have, provided the syntax were fitting. I don't like the idea of a new keyword in this case. Sean
Jul 21 2004
prev sibling next sibling parent reply Russ Lewis <spamhole-2001-07-16 deming-os.org> writes:
I assume that you are aware how you can hand-code a multimethod?

I agree that this would be very cool to be implemented automatically.

nail wrote:
 What about multimethods? It would be fine if I can do following:
 
 class Figure { ... }
 class Rect : Figure { ... }
 class Circle: Figure { ... }
 class Triangle: Figure { ... }
 
 float IntersectionSquare(Rect r, Circle c) // (1)
 { ... }
 
 float IntersectionSquare(Circle r, Circle c) // (2)
 { ... }
 
 float IntersectionSquare(Rect r, Figure f) // (3)
 { ... }
 
 multimethod float IntersectionSquare(Figure a, Figure b);
 
 void main()
 {
 Figure r = new Rect;
 Figure c = new Circle;
 Figure t = new Triangle;
 float sq;
 
 sq = IntersectionSquare(r, c); // (1) called
 sq = IntersectionSquare(c, c); // (2) called
 sq = IntersectionSquare(r, t); // (3) called
 }
 
 Any facts against?
 
 

Jul 20 2004
parent nail <nail_member pathlink.com> writes:
In article <cdkc7v$2217$1 digitaldaemon.com>, Russ Lewis says...
I assume that you are aware how you can hand-code a multimethod?

I agree that this would be very cool to be implemented automatically.

nail wrote:
 What about multimethods? It would be fine if I can do following:
 
 class Figure { ... }
 class Rect : Figure { ... }
 class Circle: Figure { ... }
 class Triangle: Figure { ... }
 
 float IntersectionSquare(Rect r, Circle c) // (1)
 { ... }
 
 float IntersectionSquare(Circle r, Circle c) // (2)
 { ... }
 
 float IntersectionSquare(Rect r, Figure f) // (3)
 { ... }
 
 multimethod float IntersectionSquare(Figure a, Figure b);
 
 void main()
 {
 Figure r = new Rect;
 Figure c = new Circle;
 Figure t = new Triangle;
 float sq;
 
 sq = IntersectionSquare(r, c); // (1) called
 sq = IntersectionSquare(c, c); // (2) called
 sq = IntersectionSquare(r, t); // (3) called
 }
 
 Any facts against?
 
 


Hand-coded multimethods are posible. But I have to use bulky auxiliary constructions a-la dispatcher. This makes code less readable and more comlicated. Integrated feature will resolve this problem in elegant way. Sean Kelly <sean f4.ca> says...
 Why the new keyword?  Doesn't D handle overload resolution just fine without
it?

No, it would not. If I have: class A {} class B : A {} void foo(A a1, A a2) { std.c.printf("A!"); } void foo(B b1, B b2) { std.c.printf("B!"); } void main() { A x = new B; A y = new B; foo(x, y); } Program will print "A!" that is not what I want.
Jul 20 2004
prev sibling next sibling parent reply Matthias Becker <Matthias_member pathlink.com> writes:
What about multimethods? It would be fine if I can do following:

class Figure { ... }
class Rect : Figure { ... }
class Circle: Figure { ... }
class Triangle: Figure { ... }

float IntersectionSquare(Rect r, Circle c) // (1)
{ ... }

float IntersectionSquare(Circle r, Circle c) // (2)
{ ... }

float IntersectionSquare(Rect r, Figure f) // (3)
{ ... }

multimethod float IntersectionSquare(Figure a, Figure b);

void main()
{
Figure r = new Rect;
Figure c = new Circle;
Figure t = new Triangle;
float sq;

sq = IntersectionSquare(r, c); // (1) called
sq = IntersectionSquare(c, c); // (2) called
sq = IntersectionSquare(r, t); // (3) called
}

Any facts against?

keyword and was more general. This ability isn't neccessarilly restricted to multimethods. You could use it for any kind of runtime dispatching. To use your notation: float handle(HandleDerived1 foo) { ... } float handle(HandleDerived2 foo) { ... } multimethod float handle(HandleBase foo); You see, the name multimethod is very confusing here. -- Matthias Becker
Jul 21 2004
parent reply nail <nail_member pathlink.com> writes:
In article <cdlc89$2gai$1 digitaldaemon.com>, Matthias Becker says...
What about multimethods? It would be fine if I can do following:

class Figure { ... }
class Rect : Figure { ... }
class Circle: Figure { ... }
class Triangle: Figure { ... }

float IntersectionSquare(Rect r, Circle c) // (1)
{ ... }

float IntersectionSquare(Circle r, Circle c) // (2)
{ ... }

float IntersectionSquare(Rect r, Figure f) // (3)
{ ... }

multimethod float IntersectionSquare(Figure a, Figure b);

void main()
{
Figure r = new Rect;
Figure c = new Circle;
Figure t = new Triangle;
float sq;

sq = IntersectionSquare(r, c); // (1) called
sq = IntersectionSquare(c, c); // (2) called
sq = IntersectionSquare(r, t); // (3) called
}

Any facts against?

keyword and was more general. This ability isn't neccessarilly restricted to multimethods. You could use it for any kind of runtime dispatching. To use your notation: float handle(HandleDerived1 foo) { ... } float handle(HandleDerived2 foo) { ... } multimethod float handle(HandleBase foo); You see, the name multimethod is very confusing here. -- Matthias Becker

I see nothing confusing in the examle above. If you mean that handle(x), where x is realy HandleBase is undefined then exception must be thrown. Or maybe more acceptable way - throw compile-time error if such method for base class is undefined. I don't impose this kind of notation. I simply said that multimethods are powerful things and having them integrated in language would be very and very nicely.
Jul 21 2004
parent reply Matthias Becker <Matthias_member pathlink.com> writes:
I see nothing confusing in the examle above. If you mean that handle(x), where x
is realy HandleBase is undefined then exception must be thrown. Or maybe more
acceptable way - throw compile-time error if such method for base class is
undefined.
I don't impose this kind of notation. I simply said that multimethods are
powerful things and having them integrated in language would be very and very
nicely.

instead of only one. In my example I used it on only one object, so the name multimethod is very confusing. Have you read my proposal? // this is a fallback function bool intersect(Shape foo, Shape bar) {...} // Intersection of Triangle and Square bool intersect(Shape Triangle foo, Shape Square bar) {...} // Intersection of Square and Triangle bool intersect(Shape Square foo, Shape Triangle bar) {...} // Intersection of two Squares bool intersect(Shape Square foo, Shape Square bar) {...} You have one compiletimetype (here Shape) and one runtimetype (Here Triangle and Square). You can do things you can't with your version: class Base1{} class Base2{} class Derived1FromBase1 : Base1 {} class Derived2FromBase1 : Base1 {} class Derived1FromBase2 : Base2 {} class Derived2FromBase2 : Base2 {} void func (Base1 Derived1FromBase1 x) {...} void func (Base1 Derived2FromBase1 x) {...} void func (Base2 Derived1FromBase2 x) {...} void func (Base2 Derived2FromBase2 x) {...} So you can do: Base1 b1 = new Derived2FromBase1(); func (b1); Base2 b2 = new Derived1FromBase2(); func (b2); I don't need a new keyword and my version is more flexible. My version allows a fallback-function i none of the derived forms fits, ... . I don't want to sound arogant. -- Matthias Becker
Jul 22 2004
parent reply Sean Kelly <sean f4.ca> writes:
Matthias Becker wrote:
 
 MULTImethod. This means that you have methods whith is bound to multiple
objects
 instead of only one. In my example I used it on only one object, so the name
 multimethod is very confusing.
 
 Have you read my proposal?
 
 // this is a fallback function 
 bool intersect(Shape foo, Shape bar) 
 {...}
 
 // Intersection of Triangle and Square
 bool intersect(Shape   Triangle foo, Shape   Square bar) 
 {...}
 
 // Intersection of Square and Triangle
 bool intersect(Shape   Square foo, Shape   Triangle bar) 
 {...}
 
 // Intersection of two Squares
 bool intersect(Shape   Square foo, Shape   Square bar) 
 {...}
 
 You have one compiletimetype (here Shape) and one runtimetype (Here Triangle
and
 Square).

Good proposal. This may need to wait for better RTTI but it has the best syntax I've seen. And oddly, a very similar proposal popped up today on comp.std.c++: http://groups.google.com/groups?selm=194c1211.0407220349.3bededd3%40posting.google.com&output=gplain Sean
Jul 22 2004
next sibling parent Matthias Becker <Matthias_member pathlink.com> writes:
Good proposal.  This may need to wait for better RTTI but it has the 
best syntax I've seen. 

Thanks.
And oddly, a very similar proposal popped up 
today on comp.std.c++:

http://groups.google.com/groups?selm=194c1211.0407220349.3bededd3%40posting.google.com&output=gplain

Jul 23 2004
prev sibling parent reply Sha Chancellor <schancel pacific.net> writes:
In article <cdpp68$1a61$1 digitaldaemon.com>, Sean Kelly <sean f4.ca> 
wrote:

 Matthias Becker wrote:
 
 MULTImethod. This means that you have methods whith is bound to multiple 
 objects
 instead of only one. In my example I used it on only one object, so the 
 name
 multimethod is very confusing.
 
 Have you read my proposal?
 
 // this is a fallback function 
 bool intersect(Shape foo, Shape bar) 
 {...}
 
 // Intersection of Triangle and Square
 bool intersect(Shape   Triangle foo, Shape   Square bar) 
 {...}
 
 // Intersection of Square and Triangle
 bool intersect(Shape   Square foo, Shape   Triangle bar) 
 {...}
 
 // Intersection of two Squares
 bool intersect(Shape   Square foo, Shape   Square bar) 
 {...}
 
 You have one compiletimetype (here Shape) and one runtimetype (Here 
 Triangle and
 Square).

Good proposal. This may need to wait for better RTTI but it has the best syntax I've seen. And oddly, a very similar proposal popped up today on comp.std.c++: http://groups.google.com/groups?selm=194c1211.0407220349.3bededd3%40posting.go ogle.com&output=gplain Sean

I'm a little confused: Is this different from function overloading in C? If not, can't the compile time type be deduced from the child class? As in, why do I need Shape Triangle. Triangle seems to imply Shape to me. On the other hand, is this trying to allow you to bind one function to several child types of a class, without accepting them all? That would be something interesting, although I've never ran into a need for such a feature.
Jul 23 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <schancel-C035F9.10362623072004 digitalmars.com>, Sha Chancellor
says...
I'm a little confused:

Is this different from function overloading in C?  If not, can't the 
compile time type be deduced from the child class?  As in, why do I need 
Shape   Triangle.   Triangle seems to imply Shape to me.

The examples were a little bit simplistic. Assume I have a factory class that returns different objects based on user input: class Base {} class Derived1 : Base {} class Derived2 : Bse {} void multimethod( Base b ) {} void multimethod( Derived1 b ) {} void multimethod( Derived2 b ) {} Base build( char[] type ) { if( type == "Derived1" ) return new Derived1(); if( type == "Derived2" ) return new Derived2(); } Base b = build( "Derived1" ); multimethod( b ); In D and C++ land, multimethod(base) will be called because that is the type of the variable b. The compiler doesn't try and determine the type of the underlying instance. I saw a good example of multimethods I think on Gamasutra.Com where the programmer had optimized collision detection functions for different pairs of shapes. Because the shapes were all created dynamically at runtime it wasn't possible to rely on standard overload resolution to find the right function. And if all else fails there's always multimethod(base) to fall back on. This type of thing can be accomplished manually but the implementations tend to be complex and brittle. Having language support makes life much easier in both respects. Sean
Jul 23 2004
parent reply Sha Chancellor <schancel pacific.net> writes:
In article <cdrjj5$28sq$1 digitaldaemon.com>, Sean Kelly <sean f4.ca> 
wrote:

 In article <schancel-C035F9.10362623072004 digitalmars.com>, Sha Chancellor
 says...
I'm a little confused:

Is this different from function overloading in C?  If not, can't the 
compile time type be deduced from the child class?  As in, why do I need 
Shape   Triangle.   Triangle seems to imply Shape to me.

The examples were a little bit simplistic. Assume I have a factory class that returns different objects based on user input: class Base {} class Derived1 : Base {} class Derived2 : Bse {} void multimethod( Base b ) {} void multimethod( Derived1 b ) {} void multimethod( Derived2 b ) {} Base build( char[] type ) { if( type == "Derived1" ) return new Derived1(); if( type == "Derived2" ) return new Derived2(); } Base b = build( "Derived1" ); multimethod( b ); In D and C++ land, multimethod(base) will be called because that is the type of the variable b. The compiler doesn't try and determine the type of the underlying instance. I saw a good example of multimethods I think on Gamasutra.Com where the programmer had optimized collision detection functions for different pairs of shapes. Because the shapes were all created dynamically at runtime it wasn't possible to rely on standard overload resolution to find the right function. And if all else fails there's always multimethod(base) to fall back on. This type of thing can be accomplished manually but the implementations tend to be complex and brittle. Having language support makes life much easier in both respects. Sean

Rather than adding syntax, should the compiler just "take care of it"? Is there a reason why it can't, or is adding syntax just easier?
Jul 23 2004
parent reply Sean Kelly <sean f4.ca> writes:
In article <schancel-C9539F.11483623072004 digitalmars.com>, Sha Chancellor
says...
Rather than adding syntax, should the compiler just "take care of it"?
Is there a reason why it can't, or is adding syntax just easier?

It's mostly a matter of efficiency. Normal overload resolution is handled at compile-time, while multimethod determination is handled at run-time. If I know that only 2% of the functions I call are for dynamic objects I don't necessarily want to pay the performance cost of having all function calls resolved at run-time. This is a side-effect of D being a weird mutant conglomeration of a few different programming styles, much like C++. For a purely dynamic language you might want to look into Smalltalk. Sean
Jul 23 2004
parent reply Sha Chancellor <schancel pacific.net> writes:
In article <cdroqu$2fnt$1 digitaldaemon.com>, Sean Kelly <sean f4.ca> 
wrote:

 In article <schancel-C9539F.11483623072004 digitalmars.com>, Sha Chancellor
 says...
Rather than adding syntax, should the compiler just "take care of it"?
Is there a reason why it can't, or is adding syntax just easier?

It's mostly a matter of efficiency. Normal overload resolution is handled at compile-time, while multimethod determination is handled at run-time. If I know that only 2% of the functions I call are for dynamic objects I don't necessarily want to pay the performance cost of having all function calls resolved at run-time. This is a side-effect of D being a weird mutant conglomeration of a few different programming styles, much like C++. For a purely dynamic language you might want to look into Smalltalk. Sean

I wasn't implying that it should be purely dynamic, I seriously dislike Obj-C and similar languages. I was implying that the compiler might be able to make the determination based on existing information rather than having to be explicitly told. Although I can imagine adding syntax makes it easier.
Jul 23 2004
parent Sean Kelly <sean f4.ca> writes:
In article <schancel-FF3823.14215323072004 digitalmars.com>, Sha Chancellor
says...
I wasn't implying that it should be purely dynamic, I seriously dislike 
Obj-C and similar languages.   I was implying that the compiler might be 
able to make the determination based on existing information rather than 
having to be explicitly told.   Although I can imagine adding syntax 
makes it easier.

If you can suggest a method then by all means do so. It might not be too hard to do in C++ -- dynamically bind for data allocated on the heap and statically bind otherwise -- but in D there isn't such a clear distinction. The only other method I can think of would be to either do something special in the function declaration or define a new statement wrapper: class A {} class B : A {} void func( A a ) {} void func( B b ) {} A a = new B(); func( a ); // calls func(A) dynamic { func( a ); // calls func(B) if it exists, otherwise func(A) } Sean
Jul 23 2004
prev sibling parent reply Andy Friesen <andy ikagames.com> writes:
nail wrote:
 What about multimethods? It would be fine if I can do following:
 
 class Figure { ... }
 class Rect : Figure { ... }
 class Circle: Figure { ... }
 class Triangle: Figure { ... }
 
 float IntersectionSquare(Rect r, Circle c) // (1)
 { ... }
 
 float IntersectionSquare(Circle r, Circle c) // (2)
 { ... }
 
 float IntersectionSquare(Rect r, Figure f) // (3)
 { ... }
 
 multimethod float IntersectionSquare(Figure a, Figure b);
 
 void main()
 {
 Figure r = new Rect;
 Figure c = new Circle;
 Figure t = new Triangle;
 float sq;
 
 sq = IntersectionSquare(r, c); // (1) called
 sq = IntersectionSquare(c, c); // (2) called
 sq = IntersectionSquare(r, t); // (3) called
 }
 
 Any facts against?

Dynamic dispatch can be implemented by setting up an associative array that maps ClassInfo to a delegate, and writing a method that indexes the classinfo of its argument and calls. The trick, then, becomes working out some automatic (or nearly automatic) way of filling up that associative array. :) -- andy
Jul 21 2004
parent reply pragma <EricAnderton at yahoo dot com> <pragma_member pathlink.com> writes:
In article <cdm3hu$2qqb$1 digitaldaemon.com>, Andy Friesen says...
 Any facts against?

Dynamic dispatch can be implemented by setting up an associative array that maps ClassInfo to a delegate, and writing a method that indexes the classinfo of its argument and calls. The trick, then, becomes working out some automatic (or nearly automatic) way of filling up that associative array. :)

Actually, I started work on a solution for this the beginning of the thread. The result is almost exactly what you propose here Andy. ( well, mostly ) ;) Take a look at the example in main, it pretty much handles the proposed case menioned at the beginning of the thread. Personally, I hate template-heavy solutions but that was the only way I could figure out how to capture the call signatures of each delegate. The only place type safety breaks is in the return value, which is broken at the moment. Basically the interesting stuff is all in the Delegate class templates. Each Delegate takes a return type and n types that correspond to the parameters of a function. The template constructor requires a valid delegate that matches this signature. The class itself exposes several methods for calling the underlying method, as well as validating the types of the passed parameters in a strict or relaxed fashion. The MultimethodDelegate itself maintains a list of abstract delegates for call dispatching. When a call is made via opCall(...) it looks for the first best match and forwards the arguments to that delegate. Another drawback is that the delegate templates are specialized by the number of parameters and the return type. So Delegate0 has no args, Delegate1 has one, Delegate2 has two args... the rest (Delegate3 on up) are left as an exercise to the developer. As far as I know, this is an age-old limitation to Templates that comes from D's C++ heritage. As a bonus, I threw in some extra helper templates and a MulticastDelegate that I was finally able to finish thanks to the ideas put forward here in the NG. If you have any fixes/changes/suggestions, please post them back here so we can all benefit. :) - Pragma
Jul 21 2004
parent pragma <EricAnderton at yahoo dot com> <pragma_member pathlink.com> writes:
Alternate link: http://www.djsolaries.com/pragma/misc/multimethod.d
Jul 21 2004
prev sibling parent "Bent Rasmussen" <exo bent-rasmussen.info> writes:
 Since D is not going to have implicit instantiation, I suggest that we

 have the autotype keyword, which will be akin to the semantics proposed

 in C++ 0.x. That is to say, the type is deduced, at compile time - it's no
 variant type! - from the initialising expression, so:

While searching for threads about implicit template instantiation I tripped over this: "Since D is not going to have implicit instantiation". Is that "not" as in "not ever" or as in "not in a long time"? -- Bent Rasmussen
Jul 28 2004