www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - Full closures for D

reply Walter Bright <newshound1 digitalmars.com> writes:
D 2.007 brings full closures to D. I was tired of D being denigrated for 
not having "real" closures.

Here are some test cases for it, so you can see what it does:
-------------------------------------------

struct S { int a,b,c,d; }

alias int delegate() dg_t;
alias int delegate(int) dg1_t;

void fill()
{
     int[100] x;
}

/************************************/

dg_t foo()
{
     int x = 7;

     int bar()
     {
	return x + 3;
     }

     return &bar;
}

void test1()
{
     dg_t dg = foo();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo2()
{
     dg_t abc()
     {
	int x = 7;

	int bar()
	{
	    return x + 3;
	}

	return &bar;
     }

     return abc();
}

void test2()
{
     dg_t dg = foo2();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo3()
{
     dg_t abc(int x)
     {
	int bar()
	{
	    return x + 3;
	}

	return &bar;
     }

     return abc(7);
}

void test3()
{
     dg_t dg = foo3();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo4()
{   S s;

     s = S(4,5,6,7);

     dg_t abc(S t)
     {
	int bar()
	{
	    return t.d + 3;
	}

	return &bar;
     }

     return abc(s);
}

void test4()
{
     dg_t dg = foo4();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

void test5()
{   int x = 7;

     dg_t abc(ref int y)
     {
	int bar()
	{   y += 4;
	    return y + 3;
	}

	return &bar;
     }

     dg_t dg = abc(x);
     fill();
     assert(x == 7);
     auto i = dg();
     assert(x == 11);
     assert(i == 14);

     x = 8;
     i = dg();
     assert(x == 12);
     assert(i == 15);
}

/************************************/

void test6()
{   int x = 7;

     dg_t abc(out int y)
     {
	int bar()
	{   y += 4;
	    return y + 3;
	}

	return &bar;
     }

     dg_t dg = abc(x);
     fill();

     assert(x == 0);
     auto i = dg();
     assert(x == 4);
     assert(i == 7);

     x = 8;
     i = dg();
     assert(x == 12);
     assert(i == 15);
}

/************************************/

void test7()
{   int[3] a = [10,11,12];

     dg_t abc(int[3] y)
     {
	int bar()
	{   y[2] += 4;
	    return y[2] + 3;
	}

	return &bar;
     }

     dg_t dg = abc(a);
     fill();

     assert(a[2] == 12);
     auto i = dg();
     assert(a[2] == 16);
     assert(i == 19);
}

/************************************/

void test8()
{   S s = S(7,8,9,10);

     dg_t abc(ref S t)
     {
	int bar()
	{   t.d += 4;
	    return t.c + 3;
	}

	return &bar;
     }

     dg_t dg = abc(s);
     fill();

     assert(s.d == 10);
     auto i = dg();
     assert(s.d == 14);
     assert(i == 12);
}

/************************************/

S foo9(out dg_t dg)
{   S s1 = S(7,8,9,10);

     dg_t abc()
     {
	int bar()
	{   s1.d += 4;
	    return s1.c + 3;
	}

	return &bar;
     }

     dg = abc();
     return s1;
}

void test9()
{   dg_t dg;

     S s = foo9(dg);
     fill();
     assert(s.a == 7);
     assert(s.b == 8);
     assert(s.c == 9);
     assert(s.d == 10);

     auto i = dg();
     assert(s.d == 10);
     assert(i == 12);
}

/************************************/

dg_t foo10()
{
     dg_t abc()
     {
	int x = 7;

	int bar()
	{
	    int def()
	    {
		return x + 3;
	    }
	    return def();
	}

	return &bar;
     }

     return abc();
}

void test10()
{
     dg_t dg = foo10();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}


/************************************/

dg_t foo11()
{
     int x = 7;

     class T
     {
	int bar()
	{
	    return x + 3;
	}
     }

     T t = new T;

     return &t.bar;
}

void test11()
{
     dg_t dg = foo11();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo12()
{
     int x = 7;

     class T
     {
	int bar()
	{
	    return x + 3;
	}

	int xyz()
	{
	    return bar();
	}
     }

     T t = new T;

     return &t.xyz;
}

void test12()
{
     dg_t dg = foo12();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo13()
{
     int x = 7;

     class T
     {
	int xyz()
	{
	    int bar()
	    {
		return x + 3;
	    }

	    return bar();
	}
     }

     T t = new T;

     return &t.xyz;
}

void test13()
{
     dg_t dg = foo13();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}


/************************************/

dg_t foo14()
{
     class T
     {
	int xyz()
	{   int x = 7;

	    int bar()
	    {
		return x + 3;
	    }

	    return bar();
	}
     }

     T t = new T;

     return &t.xyz;
}

void test14()
{
     dg_t dg = foo14();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo15()
{
     class T
     {	int x = 7;

	int xyz()
	{
	    int bar()
	    {
		return x + 3;
	    }

	    return bar();
	}
     }

     T t = new T;

     return &t.xyz;
}

void test15()
{
     dg_t dg = foo15();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 10);
}

/************************************/

dg_t foo16()
{   int a = 5;

     class T
     {	int x = 7;

	int xyz()
	{   int y = 8;
	    int bar()
	    {
		return a + x + y + 3;
	    }

	    return bar();
	}
     }

     T t = new T;

     return &t.xyz;
}

void test16()
{
     dg_t dg = foo16();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 23);
}

/************************************/

dg_t foo17()
{   int a = 5;

     class T
     {	int x = 7;

	dg_t xyz()
	{   int y = 8;

	    int bar()
	    {
		return a + x + y + 3;
	    }

	    return &bar;
	}
     }

     T t = new T;

     return t.xyz();
}

void test17()
{
     dg_t dg = foo17();
     fill();
     printf("bar = %d\n", dg());
     assert(dg() == 23);
}

/************************************/

dg_t dg18;

void bar18()
{   int a = 7;
     int foo() { return a + 3; }

     dg18 = &foo;
     int i = dg18();
     assert(i == 10);
}

void test18()
{
     bar18();
     fill();
     int i = dg18();
     assert(i == 10);
}

/************************************/

void abc19(void delegate() dg)
{
     dg();
     dg();
     dg();
}

struct S19
{
     static S19 call(int v)
     {
	S19 result;

	result.v = v;
	void nest()
	{
	    result.v += 1;
	}
	abc19(&nest);
	return result;
     }
     int a;
     int v;
     int x,y,z;
}

int foo19()
{
    auto s = S19.call(5);
    return s.v;
}

void test19()
{
     int i = foo19();
     printf("%d\n", i);
     assert(i == 8);
}

/************************************/

int main()
{
     test1();
     test2();
     test3();
     test4();
     test5();
     test6();
     test7();
     test8();
     test9();
     test10();
     test11();
     test12();
     test13();
     test14();
     test15();
     test16();
     test17();
     test18();
     test19();

     printf("Success\n");
     return 0;
}
Nov 02 2007
next sibling parent reply Denton Cockburn <diboss hotmail.com> writes:
On Fri, 02 Nov 2007 14:03:59 -0700, Walter Bright wrote:

 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.
 

Nicely Done. Lisp is looking less special everyday.
Nov 02 2007
parent reply KlausO <oberhofer users.sf.net> writes:
Denton Cockburn schrieb:
 On Fri, 02 Nov 2007 14:03:59 -0700, Walter Bright wrote:
 
 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.

Nicely Done. Lisp is looking less special everyday.

Awesome. Seems it's time to update http://www.prowiki.org/wiki4d/wiki.cgi?LanguagesVersusD and the next big thing may be first class continuations which has an interesting application in GUI code, see http://citeseer.ist.psu.edu/fuchs95escaping.html
Nov 05 2007
next sibling parent reply Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
KlausO Wrote:

 Denton Cockburn schrieb:
 On Fri, 02 Nov 2007 14:03:59 -0700, Walter Bright wrote:
 
 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.

Nicely Done. Lisp is looking less special everyday.

Awesome. Seems it's time to update http://www.prowiki.org/wiki4d/wiki.cgi?LanguagesVersusD

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.
 and the next big thing may be first class continuations which has an 
 interesting application in GUI code, see
 
 http://citeseer.ist.psu.edu/fuchs95escaping.html

Is this not possible via a library too, even without closures? Excuse me if I'm more than half asleep today.
Nov 05 2007
parent reply 0ffh <spam frankhirsch.net> writes:
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. Regards, Frank
Nov 05 2007
next sibling parent "David B. Held" <dheld codelogicconsulting.com> writes:
0ffh wrote:
 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.

You might like to use monads in a "pure multithreading" environment where the threads are written in pure functional style and are thus safely lock-free. A monad essentially allows you to defer any necessary locking to a convenient place and time. Dave
Nov 05 2007
prev sibling parent Lars Noschinski <lars-2006-1 usenet.noschinski.de> writes:
* 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.
Nov 10 2007
prev sibling parent reply 0ffh <spam frankhirsch.net> writes:
KlausO wrote:
 Awesome. Seems it's time to update
 
 http://www.prowiki.org/wiki4d/wiki.cgi?LanguagesVersusD

And it's time to distuinguish D1 vs. D2... Regards, Frank
Nov 05 2007
parent Denton Cockburn <diboss hotmail.com> writes:
On Mon, 05 Nov 2007 22:48:20 +0100, 0ffh wrote:

 KlausO wrote:
 Awesome. Seems it's time to update
 
 http://www.prowiki.org/wiki4d/wiki.cgi?LanguagesVersusD

And it's time to distuinguish D1 vs. D2... Regards, Frank

Is the goal to be able to many of these things as possible, leaving no one with a reason to use another language? :)
Nov 05 2007
prev sibling next sibling parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
Walter Bright wrote:
 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.

Very cool. I have to admit I didn't think this would make it into the language. :) I'm curious about how they work. I'd guess you dup the stack frame onto the heap when a function returns a delegate, and update the delegate's context pointer? Thanks, Nathan Reed
Nov 02 2007
parent reply Xinok <xnknet gmail.com> writes:
It seems that variables that are used by a nested function are allocated 
on the heap rather than the stack. This was my test code:

void delegate() foo(){
	int v = 60;
	int c = 35;
	writefln(&c);
	writefln(&v);
	return {writefln(&v);};
}

void main(){
	void delegate() one = foo();
	one();
}

Prints:
12FF18
8B2FF4
8B2FF4

The address of 'v' doesn't change. What you do notice is the great 
difference of the memory addresses between int v and int c, which 
suggests that 'v' is allocated on the heap rather than the stack.

Nathan Reed wrote:
 Walter Bright wrote:
 D 2.007 brings full closures to D. I was tired of D being denigrated 
 for not having "real" closures.

Very cool. I have to admit I didn't think this would make it into the language. :) I'm curious about how they work. I'd guess you dup the stack frame onto the heap when a function returns a delegate, and update the delegate's context pointer? Thanks, Nathan Reed

Nov 02 2007
parent reply "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Xinok" <xnknet gmail.com> wrote in message 
news:fgg9q8$jlr$1 digitalmars.com...
 It seems that variables that are used by a nested function are allocated 
 on the heap rather than the stack. This was my test code:

 void delegate() foo(){
 int v = 60;
 int c = 35;
 writefln(&c);
 writefln(&v);
 return {writefln(&v);};
 }

 void main(){
 void delegate() one = foo();
 one();
 }

 Prints:
 12FF18
 8B2FF4
 8B2FF4

 The address of 'v' doesn't change. What you do notice is the great 
 difference of the memory addresses between int v and int c, which suggests 
 that 'v' is allocated on the heap rather than the stack.

Hm. Don't have a D2 compiler with me -- could you run the following and tell me what it prints? void main() { int v, c; void foo() { writefln(&c); } writefln(&v); writefln(&c); } I'm wondering if the compiler is smart enough not to allocate variables on the heap if it doesn't have to. (I'm not returning foo.)
Nov 02 2007
parent reply Extrawurst <spam extrawurst.org> writes:
Content-Type: text/plain; charset=ISO-8859-15; format=flowed
Content-Transfer-Encoding: 7bit

Jarrett Billingsley schrieb:
 "Xinok" <xnknet gmail.com> wrote in message 
 news:fgg9q8$jlr$1 digitalmars.com...
   
 It seems that variables that are used by a nested function are allocated 
 on the heap rather than the stack. This was my test code:

 void delegate() foo(){
 int v = 60;
 int c = 35;
 writefln(&c);
 writefln(&v);
 return {writefln(&v);};
 }

 void main(){
 void delegate() one = foo();
 one();
 }

 Prints:
 12FF18
 8B2FF4
 8B2FF4

 The address of 'v' doesn't change. What you do notice is the great 
 difference of the memory addresses between int v and int c, which suggests 
 that 'v' is allocated on the heap rather than the stack.

     

Hm. Don't have a D2 compiler with me -- could you run the following and tell me what it prints? void main() { int v, c; void foo() { writefln(&c); } writefln(&v); writefln(&c); } I'm wondering if the compiler is smart enough not to allocate variables on the heap if it doesn't have to. (I'm not returning foo.)

Prints: 13FF28 13FF2C ...whatever that means... ~Extrawurst
Nov 02 2007
parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Extrawurst" <spam extrawurst.org> wrote in message
Prints:
13FF28
13FF2C

...whatever that means...

~Extrawurst


AGH your rich-text post is evul.

But yay, they're both on the stack, meaning the compiler at least attempts 
to be smart about it :D  No unnecessary heap allocations. 
Nov 02 2007
prev sibling next sibling parent reply 0ffh <spam frankhirsch.net> writes:
Walter Bright wrote:
 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.

Sometimes I think you must really enjoy surprising everyone.... =) Regards, Frank
Nov 02 2007
parent Walter Bright <newshound1 digitalmars.com> writes:
0ffh wrote:
 Walter Bright wrote:
 D 2.007 brings full closures to D. I was tired of D being denigrated 
 for not having "real" closures.

Sometimes I think you must really enjoy surprising everyone.... =)

Yes, and just as I was about to upload this update, the digitalmars site went down. So I sat around chewing nails for a day till it came back up <g>. BTW, the problem with the site was the ISP connection went down. The server was fine.
Nov 02 2007
prev sibling next sibling parent reply Walter Bright <newshound1 digitalmars.com> writes:
How do D closures work?

1) The compiler makes a distinction between a nested function that 
'escapes' the scope, and one that does not. It uses a very simple, but 
conservative, heuristic - did someone take the address of the function? 
If yes, it assumes the function escapes.

2) The compiler logs all local variables that are referenced by any 
nested function.

3) If any of those variables are referenced by an escaping nested 
function, or a nested function that encloses an escaping function, then 
upon function entry all the referenced variables are allocated on a 
heap-allocated chunk of memory rather than on the stack. Any referenced 
parameters are copied into that chunk. That chunk is linked into the 
nested context frames rather than the stack frame.

4) Variables not referenced by nested functions are still allocated on 
the stack.

Note you can see this at work by running obj2asm on the test code I posted.

It's not an optimal solution because the "does the function escape" 
heuristic captures too many functions.
Nov 02 2007
next sibling parent "Jarrett Billingsley" <kb3ctd2 yahoo.com> writes:
"Walter Bright" <newshound1 digitalmars.com> wrote in message 
news:fggfqq$sh5$1 digitalmars.com...
 How do D closures work?

 1) The compiler makes a distinction between a nested function that 
 'escapes' the scope, and one that does not. It uses a very simple, but 
 conservative, heuristic - did someone take the address of the function? If 
 yes, it assumes the function escapes.

 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

Indeed.. of course there might not be any other way to really check it.. One source of false positives is the #*(% # restriction where you can't forward reference nested functions, and instead have to put their addresses in local delegates. Get rid of that, and there's one problem gone..
Nov 02 2007
prev sibling next sibling parent reply "Craig Black" <craigblack2 cox.net> writes:
 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.
Nov 02 2007
next sibling parent reply Reiner Pope <some address.com> writes:
Craig Black wrote:
 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

I would have thought 'scope' would have some role in this. It currently means 'allocate on the stack', and (is this implemented yet?) when used as a storage class for function parameters, it is supposed to mean, "I won't escape this variable". You could extend the "does the function escape" heuristic to say, "the function escapes if it is assigned to a non-scope variable, or is used as a non-scope argument." This would behave correctly for what I believe is one of the major uses of functions: sort/search/etc with a predicate. These things almost never escape the predicate, so they can be written: T find(T)(ref T[] array, scope bool delegate(T t) matches) { ... } -- Reiner
Nov 03 2007
parent Reiner Pope <some address.com> writes:
Reiner Pope wrote:
 Craig Black wrote:
 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

I would have thought 'scope' would have some role in this. It currently means 'allocate on the stack', and (is this implemented yet?) when used as a storage class for function parameters, it is supposed to mean, "I won't escape this variable". You could extend the "does the function escape" heuristic to say, "the function escapes if it is assigned to a non-scope variable, or is used as a non-scope argument." This would behave correctly for what I believe is one of the major uses of functions: sort/search/etc with a predicate. These things almost never escape the predicate, so they can be written: T find(T)(ref T[] array, scope bool delegate(T t) matches) { ... } -- Reiner

Oh, and void foo() { int n = ...; ... scope dg = { return n+2; }; ... } gives you a way to tell the compiler, "dg doesn't need to be a lexical closure". -- Reiner
Nov 03 2007
prev sibling parent reply David Medlock <noone nowhere.com> writes:
Craig Black wrote:
 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

First, great job Walter! I have been just lurking for some time but you are polishing and improving things nicely. Long live D! As far as closures go, would it have been possible to just copy the whole stack frame to the heap and update the references? GC would eventually reclaim it, no? -David
Nov 03 2007
parent David Medlock <noone nowhere.com> writes:
David Medlock wrote:
 Craig Black wrote:
 It's not an optimal solution because the "does the function escape" 
 heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

First, great job Walter! I have been just lurking for some time but you are polishing and improving things nicely. Long live D! As far as closures go, would it have been possible to just copy the whole stack frame to the heap and update the references? GC would eventually reclaim it, no? -David

Oops scratch that, you still need to determine *when* to do it..
Nov 03 2007
prev sibling parent reply Martin d Anjou <point14 magma.ca> writes:
  It's not an optimal solution because the "does the function escape"
  heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

A programming language called Vera uses the "shadow" keyword to acheive a somewhat related effect. From http://www.asic-world.com/vera/concurrency_control1.html: program shadow_variable { spawn_process() ; delay(100) ; spawn_process_with_shawdow(); delay(100) ; } task print(integer i) { printf(" n = %0d\n", i); } task spawn_process () { integer n; printf("In spawn_process\n"); for(n=0; n<3 ; n++) { fork print(n) ; join none } } task spawn_process_with_shawdow () { shadow integer n; printf("In spawn_process_with_shadow_variable\n"); for(n=0; n<3 ; n++) { fork print(n) ; join none } } Executing this code gives: In spawn_process n = 3 n = 3 n = 3 In spawn_process_with_shadow_variable n = 0 n = 1 n = 2 I am not a language expert, but this seemed related. Martin
Nov 06 2007
parent Bruce Adams <tortoise_74 yeah.who.co.uk> writes:
Martin d Anjou Wrote:

  It's not an optimal solution because the "does the function escape"
  heuristic captures too many functions.

You could let the programmer specify explicitly whether or not to make a function a closure or not, maybe with a key word or something.

A programming language called Vera uses the "shadow" keyword to acheive a somewhat related effect. From http://www.asic-world.com/vera/concurrency_control1.html: program shadow_variable { spawn_process() ; delay(100) ; spawn_process_with_shawdow(); delay(100) ; } task print(integer i) { printf(" n = %0d\n", i); } task spawn_process () { integer n; printf("In spawn_process\n"); for(n=0; n<3 ; n++) { fork print(n) ; join none } } task spawn_process_with_shawdow () { shadow integer n; printf("In spawn_process_with_shadow_variable\n"); for(n=0; n<3 ; n++) { fork print(n) ; join none } } Executing this code gives: In spawn_process n = 3 n = 3 n = 3 In spawn_process_with_shadow_variable n = 0 n = 1 n = 2 I am not a language expert, but this seemed related. Martin

I see the analogy but this is actually about whether a variable should be shared between 2 threads / processes. I don't think there is a native D equivalent but "shared" has been proposed several times. This would be more like "export".
Nov 06 2007
prev sibling next sibling parent =?ISO-8859-1?Q?Julio_C=E9sar_Carrascal_Urquijo?= writes:
Walter Bright wrote:
 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.

Yay!! I still can believe it. It's certainly the feature that I wanted to see in the language the most. Thank you Walter. -- Julio Csar Carrascal Urquijo http://jcesar.artelogico.com/
Nov 02 2007
prev sibling next sibling parent reply Steve Teale <steve.teale britseyeview.com> writes:
Walter Bright Wrote:

 D 2.007 brings full closures to D. I was tired of D being denigrated for 
 not having "real" closures.
 ...

OK, I get the full closure thing, but can someone come up with a typical example of its use please?
Nov 03 2007
parent bearophile <bearophileHUGS lycos.com> writes:
Steve Teale:
 OK, I get the full closure thing, but can someone come up with a typical
example of its use please?

You are right, this may help: http://en.wikipedia.org/wiki/Closure_%28computer_science%29 Bye, bearophile
Nov 03 2007
prev sibling next sibling parent reply Witold Baryluk <baryluk smp.if.uj.edu.pl> writes:
Dnia Fri, 02 Nov 2007 14:03:59 -0700
Walter Bright <newshound1 digitalmars.com> napisa=B3/a:

 D 2.007 brings full closures to D. I was tired of D being denigrated
 for not having "real" closures.
=20

Simpler curring? // not tested C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a =3D a_; auto dg =3D dg_; C add(B b) { return dg(a, b); } return &add; } instand of: // Old code: http://www.digitalmars.com/d/template.html R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg) { struct Foo { typeof(dg) dg_m; typeof(arg) arg_m; R bar(U u) { return dg_m(arg_m, u); } } Foo* f =3D new Foo; f.dg_m =3D dg; f.arg_m =3D arg; return &f.bar; } Also other functional stuff like infinite lazy lists or monads can be done much much simpler now :) --=20 Witold Baryluk, aleph0
Nov 06 2007
parent reply outersky <outersky gmail.com> writes:
Maybe the two inner variables can be omitted  as :


C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
   C add(B b) {
     return dg_(a_, b);
   };
   return &add;
}


Witold Baryluk 写道:
 Dnia Fri, 02 Nov 2007 14:03:59 -0700
 Walter Bright <newshound1 digitalmars.com> napisał/a:
 
 D 2.007 brings full closures to D. I was tired of D being denigrated
 for not having "real" closures.

Simpler curring? // not tested C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a = a_; auto dg = dg_; C add(B b) { return dg(a, b); } return &add; } instand of: // Old code: http://www.digitalmars.com/d/template.html R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg) { struct Foo { typeof(dg) dg_m; typeof(arg) arg_m; R bar(U u) { return dg_m(arg_m, u); } } Foo* f = new Foo; f.dg_m = dg; f.arg_m = arg; return &f.bar; } Also other functional stuff like infinite lazy lists or monads can be done much much simpler now :)

Nov 06 2007
parent reply Russell Lewis <webmaster villagersonline.com> writes:
Or even:

C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
   return delegate C(B b) { return dg_(a_, b); };
}

outersky wrote:
 Maybe the two inner variables can be omitted  as :
 
 
 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
   C add(B b) {
     return dg_(a_, b);
   };
   return &add;
 }
 
 
 Witold Baryluk 写道:
 Dnia Fri, 02 Nov 2007 14:03:59 -0700
 Walter Bright <newshound1 digitalmars.com> napisał/a:

 D 2.007 brings full closures to D. I was tired of D being denigrated
 for not having "real" closures.

Simpler curring? // not tested C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a = a_; auto dg = dg_; C add(B b) { return dg(a, b); } return &add; } instand of: // Old code: http://www.digitalmars.com/d/template.html R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg) { struct Foo { typeof(dg) dg_m; typeof(arg) arg_m; R bar(U u) { return dg_m(arg_m, u); } } Foo* f = new Foo; f.dg_m = dg; f.arg_m = arg; return &f.bar; } Also other functional stuff like infinite lazy lists or monads can be done much much simpler now :)


Nov 06 2007
next sibling parent outersky <outersky gmail.com> writes:
Great!

Since it's a delegate, can it be replaced as a Closure ? If closure can 
support parameters.


Russell Lewis 写道:
 Or even:
 
 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
   return delegate C(B b) { return dg_(a_, b); };
 }
 
 outersky wrote:
 Maybe the two inner variables can be omitted  as :


 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
   C add(B b) {
     return dg_(a_, b);
   };
   return &add;
 }


 Witold Baryluk 写道:
 Dnia Fri, 02 Nov 2007 14:03:59 -0700
 Walter Bright <newshound1 digitalmars.com> napisał/a:

 D 2.007 brings full closures to D. I was tired of D being denigrated
 for not having "real" closures.

Simpler curring? // not tested C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a = a_; auto dg = dg_; C add(B b) { return dg(a, b); } return &add; } instand of: // Old code: http://www.digitalmars.com/d/template.html R delegate(U) Curry(R, A, U...)(R delegate(A, U) dg, A arg) { struct Foo { typeof(dg) dg_m; typeof(arg) arg_m; R bar(U u) { return dg_m(arg_m, u); } } Foo* f = new Foo; f.dg_m = dg; f.arg_m = arg; return &f.bar; } Also other functional stuff like infinite lazy lists or monads can be done much much simpler now :)



Nov 06 2007
prev sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
Witold Baryluk wrote:
 Dnia Tue, 06 Nov 2007 20:17:58 -0700
 Russell Lewis <webmaster villagersonline.com> napisa/a:
 
 Or even:

 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
    return delegate C(B b) { return dg_(a_, b); };
 }

Yes i was thinking about it, but didn't know if it should work. I just installed 2.007, and it is working! :) import std.stdio; C delegate(B) curry1(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a = a_; auto dg = dg_; C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry2(A, C, B...)(C delegate(A, B) dg, A a) { C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry3(A, C, B...)(C delegate(A, B) dg, A a) { return delegate C(B b) { return dg(a, b); }; } int main(char[][] args) { int sum(int a, int b, int c) { return a+b+c; } auto s1 = curry1(&sum, 11000); assert(s1(200,30) == 11230); auto s2 = curry2(&sum, 21000); assert(s2(200,30) == 21230); auto s3 = curry3(&sum, 31000); assert(s3(200,30) == 31230); return 0; }

Pedantic's note: currying an argument actually means creating a function that accepts a function of N arguments and returns a function that accepts 1 argument and returns a function that accepts N-1 arguments. But I don't really care if you want to call partial application currying. Just be aware that you're using the term in a way many would deem incorrect. Random link: http://srfi.schemers.org/srfi-26/mail-archive/msg00015.html --bb
Nov 06 2007
parent Bill Baxter <dnewsgroup billbaxter.com> writes:
Bill Baxter wrote:
 Witold Baryluk wrote:
 Dnia Tue, 06 Nov 2007 20:17:58 -0700
 Russell Lewis <webmaster villagersonline.com> napisa/a:

 Or even:

 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
    return delegate C(B b) { return dg_(a_, b); };
 }

Yes i was thinking about it, but didn't know if it should work. I just installed 2.007, and it is working! :) import std.stdio; C delegate(B) curry1(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a = a_; auto dg = dg_; C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry2(A, C, B...)(C delegate(A, B) dg, A a) { C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry3(A, C, B...)(C delegate(A, B) dg, A a) { return delegate C(B b) { return dg(a, b); }; } int main(char[][] args) { int sum(int a, int b, int c) { return a+b+c; } auto s1 = curry1(&sum, 11000); assert(s1(200,30) == 11230); auto s2 = curry2(&sum, 21000); assert(s2(200,30) == 21230); auto s3 = curry3(&sum, 31000); assert(s3(200,30) == 31230); return 0; }

Pedantic's note:

Pedant's note: a pedantic person is called a "pedant". --bb
Nov 06 2007
prev sibling next sibling parent Witold Baryluk <baryluk smp.if.uj.edu.pl> writes:
Dnia Tue, 06 Nov 2007 20:17:58 -0700
Russell Lewis <webmaster villagersonline.com> napisa=B3/a:

 Or even:
=20
 C delegate(B) curry(A, C, B...)(C delegate(A, B) dg_, A a_) {
    return delegate C(B b) { return dg_(a_, b); };
 }

Yes i was thinking about it, but didn't know if it should work. I just installed 2.007, and it is working! :) import std.stdio; C delegate(B) curry1(A, C, B...)(C delegate(A, B) dg_, A a_) { auto a =3D a_; auto dg =3D dg_; C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry2(A, C, B...)(C delegate(A, B) dg, A a) { C add(B b) { return dg(a, b); } return &add; } C delegate(B) curry3(A, C, B...)(C delegate(A, B) dg, A a) { return delegate C(B b) { return dg(a, b); }; } int main(char[][] args) { int sum(int a, int b, int c) { return a+b+c; } auto s1 =3D curry1(&sum, 11000); assert(s1(200,30) =3D=3D 11230); auto s2 =3D curry2(&sum, 21000); assert(s2(200,30) =3D=3D 21230); auto s3 =3D curry3(&sum, 31000); assert(s3(200,30) =3D=3D 31230); return 0; } --=20 Witold Baryluk, aleph0 MAIL: baryluk smp.if.uj.edu.pl JID: movax jabber.autocom.pl
Nov 06 2007
prev sibling next sibling parent reply "David B. Held" <dheld codelogicconsulting.com> writes:
Ok, everyone knows that closures are cool (and those that don't know 
will soon see), so let me see what y'all think about this proposal...

Now Perl has extremely compact closures because of the special $_ and  _ 
variables that can be used to refer to the function arguments.  Just the 
other day, I wrote this handy bit of code which converts relative 
directory entries into absolute pathnames:

    my $base = "/path/to/dir/";
    my  absoluteDirs = map { $_ = $base . $_ } readdir DIR;

Whatever you think about Perl, you have to admit that this is some 
pretty sweet code.  It's about as pure as Haskell (well, obviously I'm 
modifying $_, so it's not 'pure' in the literal sense), modulo Perl's 
syntax.  Let's look at the old imperative way to do this:

    string base = "/path/to/dir/";
    string[] absoluteDirs;
    foreach (file; readdir(DIR))
    {
        absoluteDirs ~= base ~ file;
    }

This really isn't too bad, and some folks might even prefer this style, 
but when the closure variables become lengthy expressions, this becomes 
more verbose.  Note that we have to repeat absoluteDirs and file, but 
thank D that we don't have to spell out a bunch of extraneous types or 
loop mechanics.

Now given some suitable definitions (which I demonstrate at the bottom), 
this is what we can do in D, with Walter's new closures:

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

We don't have to repeat absoluteDirs, but we still have to repeat file. 
  Still, this is pretty darned close to the Perl form, so I think we're 
doing pretty well.  However, I think it would be nice to get rid of the 
delegate type declaration altogether and git ourselves an implicit 
delegate argument, like so:

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

Notice that this is just about as close to the Perl/Haskell form as D is 
likely to ever get.  And it's a thing of beauty.  Of course, this is a 
small (but real) example, so it doesn't fully illustrate the utility of 
implicit lambda args, but don't you agree that this would be cool?

Dave



----------8<----------Proof of Concept---------------8<---------------
module Test;

import std.stdio;

string[] readdir(string dir)
{
    return ["foo", "barz", "bazzz"];
}

T[] map(T)(T delegate(T) f, T[] list)
{
    T[] result;
    foreach (e; list) result ~= f(e);
    return result;
}

void main()
{
    auto base = "/path/to/dir/";
    writeln(
        map((string file) { return base ~ file; }, readdir(""))
    );
}
Nov 06 2007
next sibling parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
David B. Held wrote:
 However, I think it would be nice to get rid of the 
 delegate type declaration altogether and git ourselves an implicit 
 delegate argument, like so:
 
    auto base = "/path/to/dir/";
    auto absoluteDirs = map({ return base ~ $0; }, readdir(DIR));
 

I think you're asking for too much intelligence form the compiler here. Remember D is statically typed. Without giving the parameter type for that delegate, we can't verify that its body is well-typed or that the call to map() is well-typed. It is logically possible to infer the types in this case, but that goes way beyond the scope of what a D compiler should have to do - this isn't ML; we do type checking, but not full type inference. Thanks, Nathan Reed
Nov 06 2007
parent reply "David B. Held" <dheld codelogicconsulting.com> writes:
Nathan Reed wrote:
 David B. Held wrote:
 However, I think it would be nice to get rid of the delegate type 
 declaration altogether and git ourselves an implicit delegate 
 argument, like so:

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

I think you're asking for too much intelligence form the compiler here. Remember D is statically typed. Without giving the parameter type for that delegate, we can't verify that its body is well-typed or that the call to map() is well-typed. It is logically possible to infer the types in this case, but that goes way beyond the scope of what a D compiler should have to do - this isn't ML; we do type checking, but not full type inference.

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
Nov 06 2007
parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
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
Nov 07 2007
parent reply Simas <simas gmx.net> writes:
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?
Nov 07 2007
next sibling parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
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

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
Nov 07 2007
parent "David B. Held" <dheld codelogicconsulting.com> writes:
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
Nov 07 2007
prev sibling parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
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
Nov 07 2007
parent reply Bill Baxter <dnewsgroup billbaxter.com> writes:
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
Nov 07 2007
parent reply Nathan Reed <nathaniel.reed gmail.com> writes:
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
Nov 07 2007
next sibling parent Bill Baxter <dnewsgroup billbaxter.com> writes:
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
Nov 07 2007
prev sibling parent "David B. Held" <dheld codelogicconsulting.com> writes:
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
Nov 07 2007
prev sibling next sibling parent reply "Janice Caron" <caron800 googlemail.com> writes:
On Nov 7, 2007 6:44 AM, David B. Held <dheld codelogicconsulting.com> wrote:
     my $base = "/path/to/dir/";
     my  absoluteDirs = map { $_ = $base . $_ } readdir DIR;

 Whatever you think about Perl, you have to admit that this is some
 pretty sweet code.

Looks completely unreadable to me. It just doesn't make sense to my brain. D is C-like, not perl-like. Please let's keep it that way.
 Let's look at the old imperative way to do this:

     string base = "/path/to/dir/";
     string[] absoluteDirs;
     foreach (file; readdir(DIR))
     {
         absoluteDirs ~= base ~ file;
     }

This I understand.
 Now given some suitable definitions (which I demonstrate at the bottom),
 this is what we can do in D, with Walter's new closures:

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

 We don't have to repeat absoluteDirs, but we still have to repeat file.

But of course. It's the name of a parameter. You ALWAYS have to repeat parameter names, unless the name is "this". (Or "outer" :) ). This is what code clear and readable. In a function parameter list, you give each paramter a name, and then in the function body, you refer to each paramter by name. Perfect!
 However, I think it would be nice to get rid of the
 delegate type declaration altogether and git ourselves an implicit
 delegate argument

Generally speaking, I don't. Although that said, D already supports variadic functions, so one could declare a function as accepting a tuple, and then refer to the arguments as tuple[n]. I do not believe this would increase readability, however, in cases where functions are not actually variadic!
     auto base = "/path/to/dir/";
     auto absoluteDirs = map({ return base ~ $0; }, readdir(DIR));

Yuk! Dollar zero? Yuk! Yuk! Yuk! Please, no!
Nov 07 2007
parent "David B. Held" <dheld codelogicconsulting.com> writes:
Janice Caron wrote:
 On Nov 7, 2007 6:44 AM, David B. Held <dheld codelogicconsulting.com> wrote:
     my $base = "/path/to/dir/";
     my  absoluteDirs = map { $_ = $base . $_ } readdir DIR;

 Whatever you think about Perl, you have to admit that this is some
 pretty sweet code.

Looks completely unreadable to me. It just doesn't make sense to my brain. D is C-like, not perl-like. Please let's keep it that way.

What if I said D is BASIC-like? Is that a good thing? The main failure of Perl is that it went overboard with operators. In fact, Perl 6 has so many operators that they had to create a Periodic Table of the Operators for it: http://www.ozonehouse.com/mark/blog/code/PeriodicTable.pdf. The good thing about Perl is that it supports Functional style programming. What do you think about the STL? Good, bad, or ugly? Did you know that std::foreach(), std::transform(), std::accumulate(), etc. are all functional-style algorithms like map()?
 Let's look at the old imperative way to do this:

     string base = "/path/to/dir/";
     string[] absoluteDirs;
     foreach (file; readdir(DIR))
     {
         absoluteDirs ~= base ~ file;
     }

This I understand.

Of course. You can practically write this verbatim in BASIC and Pascal.
 Now given some suitable definitions (which I demonstrate at the bottom),
 this is what we can do in D, with Walter's new closures:

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

 We don't have to repeat absoluteDirs, but we still have to repeat file.

But of course. It's the name of a parameter. You ALWAYS have to repeat parameter names, unless the name is "this". (Or "outer" :) ). This is what code clear and readable. In a function parameter list, you give

Sorry, I couldn't resist. ;>
 each paramter a name, and then in the function body, you refer to each
 paramter by name. Perfect!

But what about closures? In the function body you're referring to variables that you *didn't* declare in the parameter list!
 However, I think it would be nice to get rid of the
 delegate type declaration altogether and git ourselves an implicit
 delegate argument

Generally speaking, I don't. Although that said, D already supports variadic functions, so one could declare a function as accepting a tuple, and then refer to the arguments as tuple[n]. I do not believe this would increase readability, however, in cases where functions are not actually variadic!

Agreed, which is why I dislike Perl's _ variable (which is exactly Tuple[n] for Perl). But let's think about 'this'. You know, there was a time when 'this' was always spelled out: void push(Stack stack, void* item); void pop(Stack stack); void* top(Stack stack); size_t size(Stack stack); Like you said, you declare it in the parameter list, you use it in the function body, right? This must mean that C is superior to C++! Why do we tolerate that first variable disappearing from the function signatures like that? Why, that sounds suspiciously like an implicit parameter!
     auto base = "/path/to/dir/";
     auto absoluteDirs = map({ return base ~ $0; }, readdir(DIR));

Yuk! Dollar zero? Yuk! Yuk! Yuk! Please, no!

Well, there's many ways we could name implicit parameters. Most likely, it will require using numbers in some way (unless you think that letters are better); but if it's just the syntax that bothers you, I'm open to suggestions. Dave
Nov 07 2007
prev sibling parent Robert Fraser <fraserofthenight gmail.com> writes:
David B. Held Wrote:

However, I think it would be nice to get rid of the 
 delegate type declaration altogether and git ourselves an implicit 
 delegate argument, like so:
 
     auto base = "/path/to/dir/";
     auto absoluteDirs = map({ return base ~ $0; }, readdir(DIR));

At first blush, it sounded like a really good idea to me (I use Perl quite a bit for hacking out quick scripts, though never in a functional manner). The problem, I feel, is the intended audience and purpose of a language. Implicit function arguments, as well as being hard to implement, make code less readable and auditable. While Perl is great for smaller projects, having worked on a legacy system with ~1.5 million lines of Perl code, I can say it doesn't scale very well at all, and the lack of named arguments is a BIG part of that (since it takes longer to figure out what a subroutine does). Plus, in a big function/closure, you might not even be able to figure out how many arguments it needs without reading through the code. In other words, implicit function parameters (whether inside or outside a closure) make hacking out a quick piece of code easy, but if you're working with a team of different experience levels or on a huge 100-man project, I could see that causing trouble. * I say this without any background in functional anything (hopefully this spring I'll take a course in it and learn what all the fuss is about), but from what I know, pure-functional languages are rarely used in large systems.
Nov 07 2007
prev sibling parent Frank Benoit <keinfarbton googlemail.com> writes:
the lazy keyword was introduced with a hint to the logging use case.
"Save the performance of evaluating the string if logging is disabled."

  void log( lazy char[] msg ){ ... }

  ...
  char[] str = f();
  log( "my message: "~str );

With full closures, is this an implicit "taking an address of a local
function" that triggers a heap allocated stack frame?

If yes, the stack frame is allocated without the need for. this would be
counter productive in the logging use case.

If no, everything is perfect i think. :)
Nov 11 2007