November 07, 2007
David B. Held wrote:
> In order to analyze this properly, we need to look at my (working) definition of map():
> 
> T[] map(T)(T delegate(T) f, T[] list)
> {
>    T[] result;
>    foreach (e; list) result ~= f(e);
>    return result;
> }
> 
> Ok, what I propose is that
> 
>    typeof({ return base ~ $0; }) == T delegate(T)
> 
> which is easy enough to infer, because that is the declared type of f, to which the delegate is bound!  No brain surgery there.  How do we know what T is?  Well, that's easy enough to infer from list.  And you're done.  The mechanics of it isn't that hard.  The question is whether the idea of implicit arguments makes people's stomachs turn or not.
> 
> Dave

Of course, in this case it's "easy enough to infer".  However, function bodies can be arbitrarily complicated, meaning you will need a full type inference engine for this to work in the general case.

That adds so much complexity to the compiler that it's simply not worth it for the slight extra convenience.  Besides, it's just fundamentally not in the philosophy of D to do things like this.  As other people have pointed out, if you want to program like you would in Perl, you should use...Perl.

Thanks,
Nathan Reed
November 07, 2007
Nathan Reed Wrote:

> David B. Held wrote:
> > In order to analyze this properly, we need to look at my (working) definition of map():
> > 
> > T[] map(T)(T delegate(T) f, T[] list)
> > {
> >    T[] result;
> >    foreach (e; list) result ~= f(e);
> >    return result;
> > }
> > 
> > Ok, what I propose is that
> > 
> >    typeof({ return base ~ $0; }) == T delegate(T)
> > 
> > which is easy enough to infer, because that is the declared type of f, to which the delegate is bound!  No brain surgery there.  How do we know what T is?  Well, that's easy enough to infer from list.  And you're done.  The mechanics of it isn't that hard.  The question is whether the idea of implicit arguments makes people's stomachs turn or not.
> > 
> > Dave
> 
> Of course, in this case it's "easy enough to infer".  However, function bodies can be arbitrarily complicated, meaning you will need a full type inference engine for this to work in the general case.

Not really. The compiler knows what the return-type is.

> 
> That adds so much complexity to the compiler that it's simply not worth it for the slight extra convenience.  Besides, it's just fundamentally not in the philosophy of D to do things like this.  As other people have pointed out, if you want to program like you would in Perl, you should use...Perl.

On "http://www.digitalmars.com/d/" D is defined as "Its focus is on combining the power and high performance of C and C++ with the programmer productivity of modern languages like Ruby and Python."

Well, somebody may say perl isn't modern. But in Ruby and Python this is also possible. So, why is this wrong? Only why this is not C-style?


November 07, 2007
Simas wrote:
> Nathan Reed Wrote:
> 
>> David B. Held wrote:
>>> In order to analyze this properly, we need to look at my (working) definition of map():
>>>
>>> T[] map(T)(T delegate(T) f, T[] list)
>>> {
>>>    T[] result;
>>>    foreach (e; list) result ~= f(e);
>>>    return result;
>>> }
>>>
>>> Ok, what I propose is that
>>>
>>>    typeof({ return base ~ $0; }) == T delegate(T)
>>>
>>> which is easy enough to infer, because that is the declared type of f, to which the delegate is bound!  No brain surgery there.  How do we know what T is?  Well, that's easy enough to infer from list.  And you're done.  The mechanics of it isn't that hard.  The question is whether the idea of implicit arguments makes people's stomachs turn or not.
>>>
>>> Dave
>> Of course, in this case it's "easy enough to infer".  However, function bodies can be arbitrarily complicated, meaning you will need a full type inference engine for this to work in the general case.
> 
> Not really. The compiler knows what the return-type is.
> 
>> That adds so much complexity to the compiler that it's simply not worth it for the slight extra convenience.  Besides, it's just fundamentally not in the philosophy of D to do things like this.  As other people have pointed out, if you want to program like you would in Perl, you should use...Perl.
> 
> On "http://www.digitalmars.com/d/" D is defined as "Its focus is on combining the power and high performance of C and C++ with the programmer productivity of modern languages like Ruby and Python."
> 
> Well, somebody may say perl isn't modern. But in Ruby and Python this is also possible. So, why is this wrong? Only why this is not C-style? 

In python the lambdas still requires arguments to be named, and use of the 'lambda' keyword.  So it'd be something like:
    absDirs = map(lambda x: base + x, readdir(DIR))

I'd prefer that middle ground to perl's magic variables.  Make it so the type can be inferred, but the user still has to give it a name.  Maybe:

   auto base = "/path/to/dir/";
   auto absoluteDirs = map((x){ return base ~ x; }, readdir(DIR));

But I find the the Python (or Perl) much easier to look at without all the (){}; business around the anonymous function.

--bb
November 07, 2007
Simas wrote:
> Nathan Reed Wrote:
>> Of course, in this case it's "easy enough to infer".  However, function bodies can be arbitrarily complicated, meaning you will need a full type inference engine for this to work in the general case.
> 
> Not really. The compiler knows what the return-type is.

Not in all cases.

// What's the type of dg here?
auto dg = { return $0; }

void some_func ( int delegate (int) dg );
void some_func ( string delegate (string) dg );

// Which some_func is being called here?
some_func({ return $0; });

You can't depend on the environment in which the delegate is defined giving you any information about what the delegate's type should be. It's only a happy accident that in the particular example with 'map' posted above, the second parameter to map() tells you what the type T is and therefore what the delegate's type should be.  So, in the general case, you *will* need a full type inference engine to descend into the body of the delegate, examine the expressions in which parameters appear, and infer their types (e.g.: base ~ $0 where base is const(char)[] and ~ is an operation that works on two arrays of the same type, hence $0 is also const(char)[]).  And in some cases even that's not enough - the identity delegate I used above, { return $0; }, cannot reasonably be assigned *any* delegate type, since its parameter and return could be any type at all.

I'm sorry, this idea is just fundamentally not compatible with D's type system.  True, the language *could be* modified in such a way as to make this work.  But that is way too far of a departure from D's C/C++ roots.  It is not a scripting language.

> On "http://www.digitalmars.com/d/" D is defined as "Its focus is on combining the power and high performance of C and C++ with the programmer productivity of modern languages like Ruby and Python."
> 
> Well, somebody may say perl isn't modern. But in Ruby and Python this is also possible. So, why is this wrong? Only why this is not C-style? 
> 

Ruby, Python, and Perl are all dynamically typed languages, and they owe a good deal of their "programmer productivity" to that.  D wants to be progammer-productive too, but D is committed to being *statically* type-safe as much as possible.  So D will bring in only those features of modern dynamic languages that are compatible with strong, static typing.

Thanks,
Nathan Reed
November 07, 2007
Nathan Reed wrote:
> Simas wrote:
>> Nathan Reed Wrote:
>>> Of course, in this case it's "easy enough to infer".  However, function bodies can be arbitrarily complicated, meaning you will need a full type inference engine for this to work in the general case.
>>
>> Not really. The compiler knows what the return-type is.
> 
> Not in all cases.
> 
> // What's the type of dg here?
> auto dg = { return $0; }
> 
> void some_func ( int delegate (int) dg );
> void some_func ( string delegate (string) dg );
> 
> // Which some_func is being called here?
> some_func({ return $0; });
> 
> You can't depend on the environment in which the delegate is defined giving you any information about what the delegate's type should be. It's only a happy accident that in the particular example with 'map' posted above, the second parameter to map() tells you what the type T is and therefore what the delegate's type should be.  So, in the general case, you *will* need a full type inference engine to descend into the body of the delegate, examine the expressions in which parameters appear, and infer their types (e.g.: base ~ $0 where base is const(char)[] and ~ is an operation that works on two arrays of the same type, hence $0 is also const(char)[]).  And in some cases even that's not enough - the identity delegate I used above, { return $0; }, cannot reasonably be assigned *any* delegate type, since its parameter and return could be any type at all.
> 
> I'm sorry, this idea is just fundamentally not compatible with D's type system.  True, the language *could be* modified in such a way as to make this work.  But that is way too far of a departure from D's C/C++ roots.  It is not a scripting language.

More sophisticated type inference is certainly possible with a strong static typing.  When you trigger an ambiguous case like the ones you show above, the compiler should just tell you "cannot deduce type of ___ in expression ___".  Maybe with a few possible valid deductions to point out the ambiguity.  That's what the strong statically typed language ML does (and variants like OCaml).  It just happens that type inference hasn't historically been a big part of C/C++.  But D makes a number of moves in the direction of more ML-like type inference.  It's just not all the way there.  Deducing function return types would be a next logical step.

>> On "http://www.digitalmars.com/d/" D is defined as "Its focus is on combining the power and high performance of C and C++ with the programmer productivity of modern languages like Ruby and Python."
>>
>> Well, somebody may say perl isn't modern. But in Ruby and Python this is also possible. So, why is this wrong? Only why this is not C-style?
> 
> Ruby, Python, and Perl are all dynamically typed languages, and they owe a good deal of their "programmer productivity" to that.  D wants to be progammer-productive too, but D is committed to being *statically* type-safe as much as possible.  So D will bring in only those features of modern dynamic languages that are compatible with strong, static typing.

What I said above.  Aggressive type inference is perfectly compatible with strong static typing.

--bb
November 07, 2007
Bill Baxter wrote:
> More sophisticated type inference is certainly possible with a strong static typing.  When you trigger an ambiguous case like the ones you show above, the compiler should just tell you "cannot deduce type of ___ in expression ___".  Maybe with a few possible valid deductions to point out the ambiguity.  That's what the strong statically typed language ML does (and variants like OCaml).

I'm familiar with ML; in fact, I use it extensively.  I never claimed this couldn't be done, I just think it's too big of a departure from D's C/C++ roots.

> It just happens that type inference hasn't historically been a big part of C/C++.  But D makes a number of moves in the direction of more ML-like type inference.  It's just not all the way there.  Deducing function return types would be a next logical step.

The only cases I'm aware of where D does any kind of type inference is in assigning `auto' variables (which is trivial, since the type-checker generates a type for the expression on the rhs anyway), and in template argument deduction / IFTI (which C++ has too).  If there are other cases I'd love to hear about them.

Anyway, deducing function *return* types would indeed be not much different from deducing types for `auto' variables, but deducing function *argument* types is another beast altogether - as I've tried to demonstrate above.

Thanks,
Nathan Reed
November 07, 2007
Nathan Reed wrote:
> Bill Baxter wrote:
>> More sophisticated type inference is certainly possible with a strong static typing.  When you trigger an ambiguous case like the ones you show above, the compiler should just tell you "cannot deduce type of ___ in expression ___".  Maybe with a few possible valid deductions to point out the ambiguity.  That's what the strong statically typed language ML does (and variants like OCaml).
> 
> I'm familiar with ML; in fact, I use it extensively.  I never claimed this couldn't be done, I just think it's too big of a departure from D's C/C++ roots.

Ok, you probably know better than me, then.  You just seemed to be equating better type inference with scripting languages in your previous post.

>> It just happens that type inference hasn't historically been a big part of C/C++.  But D makes a number of moves in the direction of more ML-like type inference.  It's just not all the way there.  Deducing function return types would be a next logical step.
> 
> The only cases I'm aware of where D does any kind of type inference is in assigning `auto' variables (which is trivial, since the type-checker generates a type for the expression on the rhs anyway), and in template argument deduction / IFTI (which C++ has too).  If there are other cases I'd love to hear about them.

I was thinking of the type deduction in foreach which deduces the type from signatures of opApply functions.  I suppose that's pretty similar to IFTI.  But it doesn't really matter.  My main point was that D could do more than it does now.  That would still hold even if it did absolutely no inference of any kind.

> Anyway, deducing function *return* types would indeed be not much different from deducing types for `auto' variables, but deducing function *argument* types is another beast altogether - as I've tried to demonstrate above.

Sure I can believe it's different.  But that doesn't mean it wouldn't be a good addition to the language.   Just make the compiler generate errors on ambiguities.

Also you gave the example:
  void some_func ( int delegate (int) dg );
  void some_func ( string delegate (string) dg );
  // Which some_func is being called here?
  some_func({ return $0; });

But you don't need $0 to create an ambiguity.
  void some_func ( long x);
  void some_func ( uint x);
  some_func(4);
also gives you errors about ambiguity.  So your example would too.

Ambiguity isn't a killer as long as a) it's possible to recognize the ambiguity when it appears, and b) there's a significant number practically useful examples that aren't ambiguous.

The killer is probably just how difficult it would be to implement without wrecking anything.

--bb
November 08, 2007
Nathan Reed wrote:
> Bill Baxter wrote:
>> More sophisticated type inference is certainly possible with a strong static typing.  When you trigger an ambiguous case like the ones you show above, the compiler should just tell you "cannot deduce type of ___ in expression ___".  Maybe with a few possible valid deductions to point out the ambiguity.  That's what the strong statically typed language ML does (and variants like OCaml).
> 
> I'm familiar with ML; in fact, I use it extensively.  I never claimed this couldn't be done, I just think it's too big of a departure from D's C/C++ roots.

Guess what?  Deducing argument types is no harder than choosing the correct return type for min(T, U) (which is why Andrei posed it as a challenge some time ago...it's a hard problem which nicely illustrates the difficulties of type inference in an example so small anyone can understand it).  Saying that D can't do this level of type inference is equivalent to saying that you can't write a proper implementation of min() in D, which is awfully defeatist, IMO.  I think we should *strive* to get a good implementation of min(), and upon doing so, would have sufficient type deduction machinery to implement these lambdas.

> [...]
> Anyway, deducing function *return* types would indeed be not much different from deducing types for `auto' variables, but deducing function *argument* types is another beast altogether - as I've tried to demonstrate above.

It's a different problem, granted; but I don't see it as fundamentally harder.  Ambiguity errors are a straw man.  The hard stuff is when you have to do things like unification of types that people expect to work together, but have problematic subtleties (like int/uint).

Dave
November 08, 2007
Bill Baxter wrote:
> [...]
> In python the lambdas still requires arguments to be named, and use of the 'lambda' keyword.  So it'd be something like:
>     absDirs = map(lambda x: base + x, readdir(DIR))
> 
> I'd prefer that middle ground to perl's magic variables.  Make it so the type can be inferred, but the user still has to give it a name.  Maybe:
> 
>    auto base = "/path/to/dir/";
>    auto absoluteDirs = map((x){ return base ~ x; }, readdir(DIR));
> 
> But I find the the Python (or Perl) much easier to look at without all the (){}; business around the anonymous function.

I agree that anonymous lambda args are probably going a step too far. Andrei proposed the same as you (type-inferred named args), and I think it's a good compromise.

Dave
November 10, 2007
* 0ffh <spam@frankhirsch.net> [07-11-05 22:41]:
>Bruce Adams wrote:
>>KlausO Wrote:
>>Someone with more a functional programming background may be able to
>>enlighten us but doesn't the existence of proper closures allow us to
>>implement monads as a library now? Though I can't quite see in what
>>situation we would want to use them.
>
>Yup, IIRC the poor strictly functional guys needs them for I/O, but as
>we're imperative anyway I don't see any applications for monads either.

Monads have other applications, too. You can use them for error handling
(in a similar way NaN works in floating point operations) for example.