www.digitalmars.com         C & C++   DMDScript  

digitalmars.D - About objective programming

reply Antti <Antti_member pathlink.com> writes:
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;
}
May 15 2005
next sibling parent reply "Unknown W. Brackets" <unknown simplemachines.org> writes:
In the current version:

template foo(int i)
{
	int foo()
	{
		return 10 / i;
	}
}
template foo(int i: 0)
{
	int foo()
	{
		return 0;
	}
}

In the next version:

template foo(int i)
{
	int foo()
	{
		static if (i == 0)
			return 0;
		else
			return 10 / i;
	}
}

Both are used in the following way:

int main()
{
	int i = 0;
	int j;

	j = foo!(i++);
	j += foo!(i++);

	return 0;
}

So, yes, it should be - and is - possible in D ;).

-[Unknown]


 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;
 }
 
 
 

May 15 2005
parent reply JimH <JimH_member pathlink.com> writes:
In article <d67aie$2p6q$1 digitaldaemon.com>, Unknown W. Brackets says...
In the current version:

template foo(int i)
{
	int foo()
	{
		return 10 / i;
	}
}
template foo(int i: 0)
{
	int foo()
	{
		return 0;
	}
}

In the next version:

template foo(int i)
{
	int foo()
	{
		static if (i == 0)
			return 0;
		else
			return 10 / i;
	}
}

Both are used in the following way:

int main()
{
	int i = 0;
	int j;

	j = foo!(i++);
	j += foo!(i++);

	return 0;
}

So, yes, it should be - and is - possible in D ;).

-[Unknown]

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. Jim
May 15 2005
next sibling parent "Unknown W. Brackets" <unknown simplemachines.org> writes:
The compiler can do basic inlining.

-[Unknown]


 In article <d67aie$2p6q$1 digitaldaemon.com>, Unknown W. Brackets says...
 
In the current version:

template foo(int i)
{
	int foo()
	{
		return 10 / i;
	}
}
template foo(int i: 0)
{
	int foo()
	{
		return 0;
	}
}

In the next version:

template foo(int i)
{
	int foo()
	{
		static if (i == 0)
			return 0;
		else
			return 10 / i;
	}
}

Both are used in the following way:

int main()
{
	int i = 0;
	int j;

	j = foo!(i++);
	j += foo!(i++);

	return 0;
}

So, yes, it should be - and is - possible in D ;).

-[Unknown]

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. Jim

May 15 2005
prev sibling parent Brian White <bcwhite precidia.com> writes:
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
prev sibling next sibling parent reply Nod <Nod_member pathlink.com> writes:
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;
}

Ah, function overloading based on parameter *value*. I like it! Though the example is tiny, the concept seems highly powerful. Would that also mean that I could do something like: void handleToken("//") { ... } void handleToken("/*") { ... } void handleToken(char[] t) { ... } and have a corresponding switch statement is generated on function invocation? Though I would assume this would add a quite bit of compiler complexity, it would close the void left by the lack of a preprocessor further, not to mention saving me a lot of typing :)
May 15 2005
parent reply David Medlock <noone nowhere.com> writes:
Nod wrote:
 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;
}

Ah, function overloading based on parameter *value*. I like it! Though the example is tiny, the concept seems highly powerful. Would that also mean that I could do something like: void handleToken("//") { ... } void handleToken("/*") { ... } void handleToken(char[] t) { ... } and have a corresponding switch statement is generated on function invocation? Though I would assume this would add a quite bit of compiler complexity, it would close the void left by the lack of a preprocessor further, not to mention saving me a lot of typing :)

This is called 'pattern matching' in the functional programming world, and its used pretty extensively. -DavidM
May 15 2005
parent Nod <Nod_member pathlink.com> writes:
In article <d681rq$6ll$1 digitaldaemon.com>, David Medlock says...
Nod wrote:
 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;
}

Ah, function overloading based on parameter *value*. I like it! Though the example is tiny, the concept seems highly powerful. Would that also mean that I could do something like: void handleToken("//") { ... } void handleToken("/*") { ... } void handleToken(char[] t) { ... } and have a corresponding switch statement is generated on function invocation? Though I would assume this would add a quite bit of compiler complexity, it would close the void left by the lack of a preprocessor further, not to mention saving me a lot of typing :)

This is called 'pattern matching' in the functional programming world, and its used pretty extensively. -DavidM

Heh, I actually had a feeling that it had something to do with functional programming. I've been thinking of checking haskell out one of these days. Well, functional or not, it looks tremendously useful. One could do away with *a lot* of explicit program flow control, and simply let the compiler "piece things together". Hmm, maybe it could also simplify some of the common cases in contract programming... Like, instead of having :void foo(int month) :in { : assert(month > 0 && month <= 12); :} :body { ... } one would simply have :void foo(int:0 < month <= 12) { ... } and both would react similarly on invalid values of course. Thoughts?
May 15 2005
prev sibling next sibling parent reply xs0 <xs0 xs0.com> writes:
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
May 15 2005
parent reply David Medlock <noone nowhere.com> writes:
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. 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. Haskell goes a step further and will not execute code until it needs the result! I am probably making myself as clear as mud... -DavidM
May 16 2005
parent reply Nod <Nod_member pathlink.com> writes:
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
parent reply MicroWizard <MicroWizard_member pathlink.com> writes:
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
parent reply David Medlock <noone nowhere.com> writes:
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 16 2005
parent reply Benji Smith <dlanguage xxagg.com> writes:
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 16 2005
next sibling parent reply Derek Parnell <derek psych.ward> writes:
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 16 2005
parent reply David Medlock <noone nowhere.com> writes:
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 16 2005
parent Derek Parnell <derek psych.ward> writes:
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 16 2005
prev sibling parent reply Nod <Nod_member pathlink.com> writes:
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 16 2005
parent reply James Dunne <james.jdunne gmail.com> writes:
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
May 18 2005
next sibling parent reply Thomas Kuehne <thomas-dloop kuehne.this-is.spam.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

James Dunne schrieb am Wed, 18 May 2005 21:55:47 +0000 (UTC):
<snip> <snip> <snip>
 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.

Ranges would certanly allow cleaner looking code. Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi9463w+/yD4P9tIRAsgxAKDCUVzGPtA0cO3HcPYhffmRJ3v3lgCeKI/0 jifpXgpONUGlVU6w6ApJ4t0= =NsTb -----END PGP SIGNATURE-----
May 18 2005
parent reply Derek Parnell <derek psych.ward> writes:
On Thu, 19 May 2005 02:30:51 +0200, Thomas Kuehne wrote:

 -----BEGIN PGP SIGNED MESSAGE-----
 Hash: SHA1
 
 James Dunne schrieb am Wed, 18 May 2005 21:55:47 +0000 (UTC):
 <snip> <snip> <snip>
 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.

Ranges would certanly allow cleaner looking code.

Though some ranges are not very intuitive ... char[] x; . . . switch (x) { case "abend" ... "define": . . . } would that be the same as ... if (x >= "abend" && x <= "define") { . . . } And would overlapping ranges be allowed? int x; . . . switch (x) { case 0 ... 5: . . . // dropthru case 3 ... 9: . . . break; default: . . . } -- Derek Melbourne, Australia 19/05/2005 9:18:30 AM
May 18 2005
parent Thomas Kuehne <thomas-dloop kuehne.this-is.spam.cn> writes:
-----BEGIN PGP SIGNED MESSAGE-----
Hash: SHA1

Derek Parnell schrieb am Thu, 19 May 2005 09:22:59 +1000:
 On Thu, 19 May 2005 02:30:51 +0200, Thomas Kuehne wrote:

 James Dunne schrieb am Wed, 18 May 2005 21:55:47 +0000 (UTC):
 <snip> <snip> <snip>
 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.

Ranges would certanly allow cleaner looking code.

Though some ranges are not very intuitive ... char[] x; . . . switch (x) { case "abend" ... "define": . . . } would that be the same as ... if (x >= "abend" && x <= "define") { . . . }

Where is the problem? (see std.string.cmp)
 And would overlapping ranges be allowed?

    int x;
    . . .
    switch (x)
    {      
       case 0 ... 5:
         . . .
         // dropthru
       case 3 ... 9:
          . . .
          break;
       default:
          . . .
    }

That should be illegal, just like int x; switch(x){ case 2: .... case 2: ... } Thomas -----BEGIN PGP SIGNATURE----- iD8DBQFCi+zQ3w+/yD4P9tIRAiROAJ9odjosyZOUEhIccRTBBTbJm/F9RACgn1It 4fq8VvJ1cnLxFUQXsKzS8Jg= =Arzw -----END PGP SIGNATURE-----
May 18 2005
prev sibling parent reply Nod <Nod_member pathlink.com> writes:
In article <d6gdl3$caf$1 digitaldaemon.com>, James Dunne says...
I'm a bit skeptical...

Skepticism is healthy.
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.

Indeed, I meant predominantly if-else statements. As you note yourself though, switch statements are not without flaws, and cannot handle all cases (pun not intended). And when switch statements are not enough, they often get if-else statements "bolted on" within, making the switch statement hard to read. With value-based overloading that is not possible, since the path selection logic is compressed to within the function parameters of the overloads.
#switch (mydata) {
#  case 'a' .. 'z' {
#    break;
#  }
#}

Yup, range handling in switch statements would be neat. Value-based overloading can also benefit from this, check out my draft proposal: digitalmars.D/23796
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.

Heh, no not with magical hand-waving :) I agree it will increase compiler complexity somewhat, but how much is really only for Walter to answer. If you think about it, the compiler must already be doing some of this for exception handling; e.g. multiple catch statements. NB: I have never written a compiler in my life, so I could be talking out of my ass.
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.

This is not meant to *replace* if-else/switch-case statements. It should only be used where it actually *does* make the code cleaner, such as the examples presented. Also You are wrongly assuming I have a functional programming background. I'm all imperative, baby. :) Besides, this thing doesn't look *that* much like functional programming does it? I just think it fits well into 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.

If you keep the overloads physically together, which you should, it won't be worse than the corresponding i-e/s-c.
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.

Hmm well, yes, but that's a bit more clutter. Speed isn't always paramount.
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.

Now that's just wrong. Complexity will not increase more than the corresponding i-e/s-c, and that complexity increases somewhere between O(N) and O(N + ln N) depending on the skill of the programmer :)
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.

Agreed. I have switched.
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.

I second that.
Regards,
James Dunne

-Nod-
May 18 2005
parent reply James Dunne <james.jdunne gmail.com> writes:
In article <d6gjl5$hac$1 digitaldaemon.com>, Nod says...
In article <d6gdl3$caf$1 digitaldaemon.com>, James Dunne says...
I'm a bit skeptical...

Skepticism is healthy.

And how! -- if anyone says "I'm a consumer whore" I'll be shocked...
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.

Indeed, I meant predominantly if-else statements. As you note yourself though, switch statements are not without flaws, and cannot handle all cases (pun not intended). And when switch statements are not enough, they often get if-else statements "bolted on" within, making the switch statement hard to read. With value-based overloading that is not possible, since the path selection logic is compressed to within the function parameters of the overloads.
#switch (mydata) {
#  case 'a' .. 'z' {
#    break;
#  }
#}

Yup, range handling in switch statements would be neat. Value-based overloading can also benefit from this, check out my draft proposal: digitalmars.D/23796
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.

Heh, no not with magical hand-waving :) I agree it will increase compiler complexity somewhat, but how much is really only for Walter to answer. If you think about it, the compiler must already be doing some of this for exception handling; e.g. multiple catch statements. NB: I have never written a compiler in my life, so I could be talking out of my ass.

Neither have I ever written a compiler, so we both could be conversing through our asses. =D
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.

This is not meant to *replace* if-else/switch-case statements. It should only be used where it actually *does* make the code cleaner, such as the examples presented. Also You are wrongly assuming I have a functional programming background. I'm all imperative, baby. :) Besides, this thing doesn't look *that* much like functional programming does it? I just think it fits well into D.

Fair enough. I think the control flow system of both C, C++, D, C#, Java, and most other C-paradigm based languages is powerful enough as it is. I'd really need more concrete examples of the usefulness of this technique of value-based overloading for me to buy into it. As of right now, I can't think of any.
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.

If you keep the overloads physically together, which you should, it won't be worse than the corresponding i-e/s-c.

Ah! There's the guy I was lookin for: "if you keep the overloads physically together" :) Thanks. Admittedly, I've seen some terrible code written by terrible coders in my life, and assuming they would keep their code readable is a pretty rough assumption, albeit common sense to us "clean coders". =D I think that this value-based overloading would be cleaner looking if it were implemented syntactically like the contract system. An in {}, out {}, and body {}... simply extending this to something like: when (i in 0 .. 2) {}, when (j == 3), when (else) {}... or something less horrible looking. But you get the idea I hope.
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.

Hmm well, yes, but that's a bit more clutter. Speed isn't always paramount.

I agree - not always; but it's a good idea to not totally ignore it :). Did someone say Java? *gun cock noise*
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.

Now that's just wrong. Complexity will not increase more than the corresponding i-e/s-c, and that complexity increases somewhere between O(N) and O(N + ln N) depending on the skill of the programmer :)

Ah damn. I've been found out. Admittedly that's where I was talking mostly out my ass. Sorry =P. Still, user-written O(1) with function pointer lookups looks much more pleasing than compiler-generated O(N) or O(N + log N) to me. Alas, to each his own.
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.

Agreed. I have switched.

Glad to know the relays have fired.
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.

I second that.
Regards,
James Dunne

-Nod-

In response to multiple posts about the case statement range-handling, it should follow the C standard specifications regarding single-valued case statements: "[no two of the case constant expressions in the same switch statement shall have the same value]". I think that pretty clearly sums up overlapping ranges. If it doesn't, then they shouldn't be allowed. Also, I think ranges should only be allowed on numeric types, since overlapping ranges of strings cannot easily be detected, and honestly - doesn't make much sense to do so. switch (mystring) { case "ape" .. "zoo": break; case "boy" .. "cat": break; } Do those overlap? If we follow the strcmp guides I think they do, but you try determining if the ranges collide in a simple and fast algorithm that the compiler can use. Regards, James Dunne
May 18 2005
parent Nod <Nod_member pathlink.com> writes:
In article <d6h6o9$11f9$1 digitaldaemon.com>, James Dunne says...
 <snippety-snap-all-over>

Fair enough. I think the control flow system of both C, C++, D, C#, Java, and most other C-paradigm based languages is powerful enough as it is. I'd really need more concrete examples of the usefulness of this technique of value-based overloading for me to buy into it. As of right now, I can't think of any.

They certainly are powerful enough. Goto is powerful enough. The rest of the control structures only make code more readable and manageable. As does this technique. Check out the latest part of the draft for some more examples: digitalmars.D/23966
Ah!  There's the guy I was lookin for: "if you keep the overloads physically
together" :)  Thanks.  Admittedly, I've seen some terrible code written by
terrible coders in my life, and assuming they would keep their code readable is
a pretty rough assumption, albeit common sense to us "clean coders". =D

Agreed: bad coders suck. Nothing will help that fact.
I think that this value-based overloading would be cleaner looking if it were
implemented syntactically like the contract system.  An in {}, out {}, and body
{}... simply extending this to something like:  when (i in 0 .. 2) {}, when (j
== 3), when (else) {}... or something less horrible looking.  But you get the
idea I hope.

That is certainly a possibility, but I think that keeping it with the parameters will also serve as a damper against overuse. With an extra block there's no limit to the amount of ranges you can throw in there.
I agree - not always; but it's a good idea to not totally ignore it :).  Did
someone say Java?  *gun cock noise*

Nice conversing with ya :) -Nod-
May 19 2005
prev sibling parent Kevin Bealer <Kevin_member pathlink.com> writes:
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 16 2005