May 16, 2005
These ideas make me very sad.

What I see:
- confusing code making teamwork a religion instead of a method (Does anyone
heard about Extreme Programming???),
- non real optimization issues, which are important in script languages and not
in a compiled language.

I hope the community keeps the definition of D clean.

Tamas Nagy


In article <d6aq6d$2cl6$1@digitaldaemon.com>, Nod says...
>
>In article <d6a46u$1mth$1@digitaldaemon.com>, David Medlock says...
>>
>>xs0 wrote:
>>> Antti wrote:
>>> 
>>>> This should be possible in D:
>>>>
>>>> inline int foo(0)
>>>> {
>>>> return 0;
>>>> }
>>>>
>>>> int foo(int i)
>>>> {
>>>> return 10/i;
>>>> }
>>>>
>>>> int main()
>>>> {
>>>> int i=0;
>>>> int j;
>>>> j=foo(i++); // if (i==0) j=0; else j=foo(i)
>>>> j+=foo(i++); // if (i==0) j+=0; else j+=foo(i)
>>>> return 0;
>>>> }
>>> 
>>> 
>>> What's wrong with
>>> 
>>> int foo(int a)
>>> {
>>>     return a ? 10/a : 0;
>>> }
>>> 
>>> I mean, I don't get the benefit, either performance-wise, or ease-of-use-wise..
>>> 
>>> 
>>> xs0
>>
>>
>>There are a few benefits which come with pattern matching.
>>
>>1. Readability.  Your example is short but it also requires the person reading the code to understand ALL the possibilities to add/remove/change one.   If each possibility has its own function, it becomes more modular.
>>
>
>True, true. On the same note, it would be possible to separate special cases from the main algorithm, thus keeping the main algorithm "clean".
>
>>
>>2.  Optimization.  List comprehensions have some opportunities for caching results which arent possible with functions.
>>
>>list  getOdd( list mylist )
>>{
>>   list result = new list();
>>   foreach( int a; mylist ) if ( !( a & 1 ) ) result.add( a );
>>   return result;
>>}
>>
>>theres no way to know(for the compiler) ahead of time that this function is cacheable( ie that if the contents of the list do not change, the result of the function won't either )
>>
>>Now try this:
>>
>>list a = mylist[ x -> ( x & 1 == 0 ) ];
>>
>>Cleaner and no state dependent results.  This need only be computed once per list.
>>
>
>Hmm, I fail to see how list comprehension relates to pattern matching... Arent they quite different creatures?
>
>And other than improving readability and reducing typing, wouldnt pattern matching be equal, performance-wise, to a regular if-else/switch construct?
>
>>Haskell goes a step further and will not execute code until it needs the result!
>>
>
>Wee... Precomputation out the window. That doesnt sound good for interactive stuff.
>
>>I am probably making myself as clear as mud...
>>-DavidM
>
>If everything was as clear as water, who would bother getting wet to see what is
>underneath?  ;)
>-Nod-
>
>


May 16, 2005
MicroWizard wrote:
> These ideas make me very sad.
> 
> What I see:
> - confusing code making teamwork a religion instead of a method (Does anyone
> heard about Extreme Programming???),
> - non real optimization issues, which are important in script languages and not
> in a compiled language.
> 
> I hope the community keeps the definition of D clean.
> 
> Tamas Nagy
> 
>


I agree with the clean statement, but I have no idea how any of my posts have the slightest thing to do with teamwork, religion or Xtreme programming?


int  factorial(0) { return 1; }
int  factorial(int n) { return n * factorial(n-1); }

Wow thats looks confusing...

-DavidM
May 17, 2005
David Medlock wrote:
> int  factorial(0) { return 1; }
> int  factorial(int n) { return n * factorial(n-1); }
> 
> Wow thats looks confusing...
> 
> -DavidM

I don't like it. It seems like another clever tricky way of doing things, when the ordinary non-tricy way works just fine. What's wrong with:

int factorial (int n) {
  if (n == 0) {
    return 0;
  } else {
    return n * factorial(n - 1);
  }
}

To me, using implicit function overloads as an alternate form of flow control is worse than using exceptions for flow control.

Clarity, people!!

--BenjiSmith
May 17, 2005
On Mon, 16 May 2005 18:15:09 -0600, Benji Smith wrote:

> David Medlock wrote:
>> int  factorial(0) { return 1; }
>> int  factorial(int n) { return n * factorial(n-1); }
>> 
>> Wow thats looks confusing...
>> 
>> -DavidM
> 
> I don't like it. It seems like another clever tricky way of doing things, when the ordinary non-tricy way works just fine. What's wrong with:
> 
> int factorial (int n) {
>    if (n == 0) {
>      return 0;
>    } else {
>      return n * factorial(n - 1);
>    }
> }
> 
> To me, using implicit function overloads as an alternate form of flow control is worse than using exceptions for flow control.
> 
> Clarity, people!!

Or maybe ...

 uint factorial (uint n) {
      return n == 0 ? 1 : n * factorial(n - 1);
 }


-- 
Derek
Melbourne, Australia
17/05/2005 10:32:16 AM
May 17, 2005
Derek Parnell wrote:
> On Mon, 16 May 2005 18:15:09 -0600, Benji Smith wrote:
> 
> 
>>David Medlock wrote:
>>
>>>int  factorial(0) { return 1; }
>>>int  factorial(int n) { return n * factorial(n-1); }
>>>
>>>Wow thats looks confusing...
>>>
>>>-DavidM
>>
>>I don't like it. It seems like another clever tricky way of doing things, when the ordinary non-tricy way works just fine. What's wrong with:
>>
>>int factorial (int n) {
>>   if (n == 0) {
>>     return 0;
>>   } else {
>>     return n * factorial(n - 1);
>>   }
>>}
>>
>>To me, using implicit function overloads as an alternate form of flow control is worse than using exceptions for flow control.
>>
>>Clarity, people!!
> 
> 
> Or maybe ...
> 
>  uint factorial (uint n) {
>       return n == 0 ? 1 : n * factorial(n - 1);
>  }
> 
> 

It doesnt replace conditionals, just particular types of them. (In languages like Prolog it is the only way to specify branches the code may take,however)

With this small example it doesn't seem like much, but extend it to say, a parser with dozens of options for the same function which are likely to be added all the time.  I personally hate multiply-nested if/then/else constructs.

The usual way to help this type of complexity is to make the program data driven(regular expressions) or use a preprocessor(lex and yacc).

I am not suggesting D adopt this however.  It does not fit with what D tries to do, be a great systems language with as few surprises as possible for the developer.

Cheers,
-DavidM
May 17, 2005
On Mon, 16 May 2005 20:46:01 -0400, David Medlock wrote:

> Derek Parnell wrote:
>> On Mon, 16 May 2005 18:15:09 -0600, Benji Smith wrote:
>> 
>>>David Medlock wrote:
>>>
>>>>int  factorial(0) { return 1; }
>>>>int  factorial(int n) { return n * factorial(n-1); }
>>>>

[snip]

>> 
>> Or maybe ...
>> 
>>  uint factorial (uint n) {
>>       return n == 0 ? 1 : n * factorial(n - 1);
>>  }
>> 
> 
> It doesnt replace conditionals, just particular types of them. (In languages like Prolog it is the only way to specify branches the code may take,however)

Oh, don't get me wrong. My 'counter example' was really just pointing out a mistake in the previous example (return 0?) as well as providing yet another alternative.

I think I like the pseudo-declarative style you have suggested. The one thing a little confusing about it is that it *looks* like a compile time evaluation but it is really a run time evaluation. That might surprise people at first. It is kind of like an elaborate switch statement...

  uint factorial (uint n) {
    switch (n) {
        case  0: return 1;
        default: return n * factorial(n - 1);
    }
  }


-- 
Derek
Melbourne, Australia
17/05/2005 11:01:17 AM
May 17, 2005
In article <d6bdbo$2sk0$1@digitaldaemon.com>, Benji Smith says...
>
>David Medlock wrote:
>> int  factorial(0) { return 1; }
>> int  factorial(int n) { return n * factorial(n-1); }
>> 
>> Wow thats looks confusing...
>> 
>> -DavidM
>
>I don't like it. It seems like another clever tricky way of doing things, when the ordinary non-tricy way works just fine. What's wrong with:
>
>int factorial (int n) {
>   if (n == 0) {
>     return 0;
>   } else {
>     return n * factorial(n - 1);
>   }
>}
>
>To me, using implicit function overloads as an alternate form of flow control is worse than using exceptions for flow control.
>
>Clarity, people!!
>
>--BenjiSmith

I agree that in the small scale of these examples, no tangible benefit can be found, but think bigger! I believe the benefit can be found when you have a large number of flow paths to select from.

In the real world you often see large-to-huge if-else/select-case statements which are *horrible* to read and maintain. Yes, one *should* break it down and refactor, but who has the time? If it works, dont touch it, right? And thus, it keeps on growing. Wouldnt it be better to let the compiler handle the kludge?

Take for example a token despatcher in a parser. They usually have the same boilerplate buildup: a large switch statement, and in each of the cases sits a function which handles the token or token class. So what is really going on here? Data values controls program flow into functions. Well, thats exactly what is suggested with pattern matching, only implicitly!

The loop gets potentially reduced to this:

:foreach (Token t; tokens)
:    handleToken(t);

.. and the compiler should generate the code necessary to select the correct handleToken overload based on the token. Now thats *increasing* clarity, not reducing it.

Also, when using pattern matching, its the *function* which decides which cases it can handle, and not, as usually, the kludge. This is how it should be. If we were to, for example, split one of the token handler functions in two, we would have to change two places: both the kludge and the function(s). Not so if using pattern matching. Since the flow control is implicit, one simply have to update the function signatures to handle each of their own cases. Clean!

As another example, a bit more real-world, take the pure Win32 Windows Message Loop (TM). I am sure most of you are familiar with it. The WML can be done in two ways, a bad one and a good one respectively: either handling things in a huge kludge as in the above example (I have seen too many of these) or, the better way is to use windowsx.h macros. These macros do nothing more than, calls a specific function for each message type. Exactly that could be accomplished by pattern matching based on function signatures, implicitly, and you would never have to touch the main loop.

Of course its not a panacea, but my point is, when used judiciously it could potentially make maintenance and refactoring easier. Other than that it would just be another tool in the chest.

btw: I call the idiom "pattern matching" because thats what DavidM said it was called in functional programming, but maybe something like "value-based overloading" would be more appropriate in D context?

Phew! That went on too long.
-Nod-


May 17, 2005
In article <d677hq$2nh7$1@digitaldaemon.com>, Antti says...
>
>This should be possible in D:
>
>inline int foo(0)
>{
>return 0;
>}
>
>int foo(int i)
>{
>return 10/i;
>}
>
>int main()
>{
>int i=0;
>int j;
>j=foo(i++); // if (i==0) j=0; else j=foo(i)
>j+=foo(i++); // if (i==0) j+=0; else j+=foo(i)
>return 0;
>}

I can see this being practical to implement, as long as the "foo(0)" version was only called for literal 0 or compile-time-known zero values.  In other words, you could optimize for some cases, but the "int i" version would need to be able to handle zero as well; thus the burden is not on the compiler, even not supporting the constant ones would be legal.

This would allow compile-time optimization for certain more-common inputs (every function's got a few of them.)

: boyer_moore_search(char[] data, char[] search_for)
: {
:     // Heavy duty version that is faster for longer search strings.
: }
:
: boyer_moore_search(char[] data, "\n")
: {
:     // special optimized version for the known 'getline' like
:     // scenario which is 90% of the calls in this program.
: }

If you pass a variable for /search_for/, it could always call the first one, even if it is equal to "\n".

Kevin


May 18, 2005
>>template foo(int i)
>>{
>>	int foo()
>>	{
>>		static if (i == 0)
>>			return 0;
>>		else
>>			return 10 / i;
>>	}
>>}
>>[...]
>
> Are you sure that's true? Sorry, I'm new to D so maybe I'm missing something.
> But in the case above, the value of i is not known until run time, whereas in
> Walter's example of static if, the parameter is a constant at compile time. 

I was thinking the same thing.  Since you're passing a run-time variable, the static-if should always evaluate false (actually an error would probably be better) for this case.

                                          Brian
                                 ( bcwhite@precidia.com )

-------------------------------------------------------------------------------
  Suffering is meaningful and can be endured... because it's worth the effort.
       What is more worthy of your pain than the evolution of your soul?
May 18, 2005
I'm a bit skeptical...

In article <d6bk3r$4t8$1@digitaldaemon.com>, Nod says...
>
>I agree that in the small scale of these examples, no tangible benefit can be found, but think bigger! I believe the benefit can be found when you have a large number of flow paths to select from.
>
>In the real world you often see large-to-huge if-else/select-case statements which are *horrible* to read and maintain. Yes, one *should* break it down and refactor, but who has the time? If it works, dont touch it, right? And thus, it keeps on growing. Wouldnt it be better to let the compiler handle the kludge?
>

Large switch blocks aren't horrible looking.  They were invented to clear up large blocks of repeated if-else definitions.  Now *those* are barely readable. The only reason some repeated if-else blocks are used is because there is a lack of range-handling by case statements.  Admittedly, switch statements can be made a bit more readable by providing each case block with its own curly-braced scope.

#switch (mydata) {
#  case 'a' .. 'z' {
#    break;
#  }
#}

>Take for example a token despatcher in a parser. They usually have the same boilerplate buildup: a large switch statement, and in each of the cases sits a function which handles the token or token class. So what is really going on here? Data values controls program flow into functions. Well, thats exactly what is suggested with pattern matching, only implicitly!
>
>The loop gets potentially reduced to this:
>
>:foreach (Token t; tokens)
>:    handleToken(t);
>
>.. and the compiler should generate the code necessary to select the correct handleToken overload based on the token.

With magical handwaving?  No, with an effectively large compiler-generated switch statement! =P We've just shoved the work on down to the compiler - and I think our current compiler has enough on its plate to deal with =P.

However, if the compiler were able to inline all of these value-based overloaded functions into the large generated switch statement, then I can see the gain in efficiency.  That probably won't happen though, and would be a lot of work to implement in the compiler.

>Now thats *increasing* clarity, not reducing it.

I can see how, in the limited number of examples proposed, that this would increase visual clarity - somewhat.  It depends how you're trained to look at it.  My eyes are trained to see switch statements calling functions based on data, and that's instantly recognizable to me - not having worked with any functional-based languages.  I think you'll find this a pattern in C++ and C programmers moving over to D.

>Also, when using pattern matching, its the *function* which decides which cases it can handle, and not, as usually, the kludge. This is how it should be. If we were to, for example, split one of the token handler functions in two, we would have to change two places: both the kludge and the function(s). Not so if using pattern matching. Since the flow control is implicit, one simply have to update the function signatures to handle each of their own cases. Clean!

Seems like a nightmare to try to maintain all the possible cases if the number of overloads gets up there.  Tell me how this would work for a straight bytecode interpreter and how it could improve readability and efficiency.

>As another example, a bit more real-world, take the pure Win32 Windows Message Loop (TM). I am sure most of you are familiar with it. The WML can be done in two ways, a bad one and a good one respectively: either handling things in a huge kludge as in the above example (I have seen too many of these) or, the better way is to use windowsx.h macros. These macros do nothing more than, calls a specific function for each message type. Exactly that could be accomplished by pattern matching based on function signatures, implicitly, and you would never have to touch the main loop.


Why not make a table of function pointers to handle the messages?  You then get
O(1) lookup on which function to call, instead of O(n) (?) as in a switch
statement.  Then again, you lose the *possible* performance benefits of the
compiler inlining your functions.


>Of course its not a panacea, but my point is, when used judiciously it could potentially make maintenance and refactoring easier. Other than that it would just be another tool in the chest.


Having this support for n number of parameters increases the complexity exponentially, not to mention the amount of typing and high potential to introduce bugs when copy/pasting definitions.


>btw: I call the idiom "pattern matching" because thats what DavidM said it was called in functional programming, but maybe something like "value-based overloading" would be more appropriate in D context?

Despite my objections above, I'd say that "value-based overloading" sounds better since we're not matching patterns.  Then again, I truly hate terminology =P, especially when a term has no latin-based root in its name or any relevance to what it describes.

>Phew! That went on too long.
>-Nod-

And I made it longer! =)

What I'd really like to see is better switch statement case handling.  The new ISOC99 standard for C now allows ranges in case statements; why not adapt D's switch statement for this?  *checks the docs*  We already have comma-separated cases, why not ranges in the form case a .. b (like Pascal did)?  I think this would improve significantly on readability of data-driven program flow.

Regards,
James Dunne