November 07, 2007
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 :)
>>>
November 07, 2007
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;
}




-- 
Witold Baryluk, aleph0
MAIL: baryluk@smp.if.uj.edu.pl
JID: movax@jabber.autocom.pl
November 07, 2007
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
November 07, 2007
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
November 07, 2007
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(""))
   );
}
November 07, 2007
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
November 07, 2007
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
November 07, 2007
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!
November 07, 2007
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
November 07, 2007
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.