|
|
|
|
||||||
![]() |
|
|
LinkBack | Outils de la discussion |
|
|
#1 |
|
Messages: n/a
Hébergeur: |
Adem24 wrote:
> 1) There is no goto statement. > Hidden goto's like break- and continue-statements are also omitted. > > 2) There is no return statement. > Instead a result variable can be declared to which the result of a function can be assigned. Is the goal of this language to make the life of programmers as hard as possible? Something like this becomes difficult to implement: // Find the first value in 'data' which meets the requirements imposed // by the parameter: Type1 foo(Type2 value) { for(size_t i = 0; i < data.size(); ++i) for(size_t j = 0; j < data[i].size(); ++j) for(size_t k = 0; k < data[i][j].size(); ++k) if(meetsRequirements(data[i][j][k], value) return data[i][j][k]; } |
|
|
|
#2 |
|
Messages: n/a
Hébergeur: |
On 30 Jun., 17:43, Juha Nieminen <nos...@thanks.invalid> wrote:
> Adem24 wrote: > > 1) There is no goto statement. > > Hidden goto's like break- and continue-statements are also omitted. > > > 2) There is no return statement. > > Instead a result variable can be declared to which the result of a function can be assigned. > > Is the goal of this language to make the life of programmers as hard > as possible? Something like this becomes difficult to implement: > > // Find the first value in 'data' which meets the requirements imposed > // by the parameter: > Type1 foo(Type2 value) > { > for(size_t i = 0; i < data.size(); ++i) > for(size_t j = 0; j < data[i].size(); ++j) > for(size_t k = 0; k < data[i][j].size(); ++k) > if(meetsRequirements(data[i][j][k], value) > return data[i][j][k]; > > } Your function returns garbage when nothing meets the requirements. Your solution may be elegant, but it is wrong (I know that it can be fixed easily). Some people would even argue that with structured programming such bugs would not happen. Besides that, I got your point. Before going to the details let me explain the advantage when every statement has exactly one entry and one exit (this is a side effect of structured programming without any form of goto's): If you want to add code that should be executed when the function is left (e.g. some trace statement) you can just add it at the end of the function. The only reason that your trace code is skipped is when an exception is raised and no exception handler inside the function catches it. If there are no exceptions you know that the flow of control is always stuctured. If you jump around in your code with gotos and returns you don't have this guarantee. A stuctured solution to your example could use a boolean flag which is changed when the data is found. It can be argued that this is slower but todays compiler optimisations should not be unerestimated. A C version of this structured function would be: // Find the first value in 'data' which meets the requirements // imposed by the parameter: Type1 foo (Type2 value) { int search = 1; Type1 result = Type1default; for (size_t i = 0; i < data.size() & search; ++i) { for (size_t j = 0; j < data[i].size() & search; ++j) { for (size_t k = 0; k < data[i][j].size() & search; ++k) { if (meetsRequirements(data[i][j][k], value) { result = data[i][j][k]; search = 0; } } } } return result; } As you can see, I prefer to use curly braces even when they are not necessary. This use of curly braces allowes that statements belonging to 'while', 'do', 'for' and 'if' statements can always be added or removed without unintentionally changing the logic. The use of a result variable makes clear which function holds the result of the function. Since C needs a return statement I just put it in front of the closing curly brace. IMHO a good compiler can produce code which is as fast as your version. In a language which has no C for loops like Seed7 the function would look as follows: const func Type1: foo (in Type2: aValue) is func result var Type1: result is Type1.value; local var integer: i is 1; var integer: j is 1; var integer: k is 1; var boolean: search is TRUE; begin while i <= maxIdx(data) and search do while j <= maxIdx(data[i]) and search do while k < maxIdx(data[i][j]) and search do if meetsRequirements(data[i][j][k], aValue) then result := data[i][j][k]; search := FALSE; end if; incr(k); end while; incr(j); end while; incr(i); end while; end func; I know that this solution is not as elegant. Maybe I should introduce an advanced version of the 'for' statement like: for i range minIdx(data) range maxIdx(data) andWhile search do What do you think? Greetings Thomas Mertes Seed7 Homepage: http://seed7.sourceforge.net Seed7 - The extensible programming language: User defined statements and operators, abstract data types, templates without special syntax, OO with interfaces and multiple dispatch, statically typed, interpreted or compiled, portable, runs under linux/unix/windows. |
|
|
|
#3 |
|
Messages: n/a
Hébergeur: |
thomas.mertes@gmx.at wrote:
>> Type1 foo(Type2 value) >> { >> for(size_t i = 0; i < data.size(); ++i) >> for(size_t j = 0; j < data[i].size(); ++j) >> for(size_t k = 0; k < data[i][j].size(); ++k) >> if(meetsRequirements(data[i][j][k], value) >> return data[i][j][k]; >> >> } > Your function returns garbage when nothing meets the requirements. Most compilers will issue a warning, though. > A stuctured solution to your example could use a boolean flag which > is changed when the data is found. Exactly how is that different from 'break'? (Let's assume a 'break' which can take as parameter how many nested loops it breaks from, like some languages have. In other words, you could do a "break 3;" in the example above, assuming it was supported.) The only difference I can see is that the 'break' would be compiler-supported (and thus easier for the compiler to optimize), while the boolean solution is user-written, which is more work and clumsier. Also, I don't believe the boolean solution would make the code any easier to understand and follow than the 'break' solution. > // Find the first value in 'data' which meets the requirements > // imposed by the parameter: > Type1 foo (Type2 value) > { > int search = 1; > Type1 result = Type1default; > > for (size_t i = 0; i < data.size() & search; ++i) { > for (size_t j = 0; j < data[i].size() & search; ++j) { > for (size_t k = 0; k < data[i][j].size() & search; ++k) { > if (meetsRequirements(data[i][j][k], value) { > result = data[i][j][k]; > search = 0; > } > } > } > } > return result; > } Another point: Assume that initializing the return value is a very heavy operation. If a result is found, you are initializing the return value twice, for no reason. (If the found value was returned from inside the loop, and if the loops end without result, the default value is returned, the return value would then always be initialized only once.) In fact, it would be enough for the default value to be very heavy to construct for this problem to happen. Sure, you could add yet another boolean to take care of that... Is this supposed to make the code easier to write and read, and/or less error-prone? > As you can see, I prefer to use curly braces even when they are not > necessary. That's mostly a matter of style and preference. I omitted them mostly for brevity (ie. it doesn't mean I consistently avoid the braces in real code whenever I can). Btw, why do you use the braces in an inconsistent way? > In a language which has no C for loops like Seed7 the function > would look as follows: > > const func Type1: foo (in Type2: aValue) is func > result > var Type1: result is Type1.value; > local > var integer: i is 1; > var integer: j is 1; > var integer: k is 1; > var boolean: search is TRUE; > begin > while i <= maxIdx(data) and search do > while j <= maxIdx(data[i]) and search do > while k < maxIdx(data[i][j]) and search do > if meetsRequirements(data[i][j][k], aValue) then > result := data[i][j][k]; > search := FALSE; > end if; > incr(k); > end while; > incr(j); > end while; > incr(i); > end while; > end func; I'm sorry but I have to confess that doesn't look like very attractive to me. The 9 lines of code in my original function (with the fix of returning the default value at the end) vs. 23 lines in your example. I honestly can't say your version is more readable either. Let's make it clear that I'm all against compact obfuscated code. However, there's a limit where code becomes *too* verbose to be comfortable. > I know that this solution is not as elegant. > Maybe I should introduce an advanced version of the 'for' statement > like: > > for i range minIdx(data) range maxIdx(data) andWhile search do > > What do you think? A 'for' clause is always a handy shortcut for 'while', so why not. I also think you should really consider 'break' (perhaps even the version which takes a parameter, as I mentioned above). |
|
|
|
#4 |
|
Messages: n/a
Hébergeur: |
[I've removed comp.lang.c from the cross-postings, since the
syntax of the proposed solutions is purely C++.] On Jul 1, 7:29 pm, Juha Nieminen <nos...@thanks.invalid> wrote: > thomas.mer...@gmx.at wrote: > >> Type1 foo(Type2 value) > >> { > >> for(size_t i = 0; i < data.size(); ++i) > >> for(size_t j = 0; j < data[i].size(); ++j) > >> for(size_t k = 0; k < data[i][j].size(); ++k) > >> if(meetsRequirements(data[i][j][k], value) > >> return data[i][j][k]; > >> } > > Your function returns garbage when nothing meets the requirements. > Most compilers will issue a warning, though. > > A stuctured solution to your example could use a boolean flag which > > is changed when the data is found. > Exactly how is that different from 'break'? It gives a name to the condition, and makes it clear to the reader what is going on. However... A structured solution would break this down into several functions. In the next release of C++ (with lambda), you could easily do the whole thing with a single call to std::find_if: the predicate for std::find_if on the outer dimension would invoke std::find_if on the next dimention, and so on. Alternatively, since you're really doing a linear search over the flattened structure, you'd use an iterator which flattened the structure. I'm pretty sure that Boost has something which would here. > (Let's assume a 'break' which can take as parameter how many > nested loops it breaks from, like some languages have. In > other words, you could do a "break 3;" in the example above, > assuming it was supported.) That is, of course, a real trap. Change the structure, and your code silently changes meaning. Having a one level break is bad enough. > The only difference I can see is that the 'break' would be > compiler-supported (and thus easier for the compiler to > optimize), while the boolean solution is user-written, which > is more work and clumsier. Also, I don't believe the boolean > solution would make the code any easier to understand and > follow than the 'break' solution. > > // Find the first value in 'data' which meets the requirements > > // imposed by the parameter: > > Type1 foo (Type2 value) > > { > > int search = 1; > > Type1 result = Type1default; > > for (size_t i = 0; i < data.size() & search; ++i) { > > for (size_t j = 0; j < data[i].size() & search; ++j) { > > for (size_t k = 0; k < data[i][j].size() & search; ++k) { > > if (meetsRequirements(data[i][j][k], value) { > > result = data[i][j][k]; > > search = 0; > > } > > } > > } > > } > > return result; > > } > Another point: Assume that initializing the return value is a very > heavy operation. Why? Since I have to consider the possibility of not finding the value, I'd use a Fallible: Fallible< Type1 > result ; for ( size_t i = 0 ; ! result.isValid() && i != data.size() ; ++ i ) { for ( size_t j = 0 ; ! result.isValid() && j != data.size() ; + + j ) { for ( size_t k = 0 ; ! result.isValid() && k != data.size() ; ++ k ) { if ( meetsRequirements( data[ i ][ j ][ k ], value ) { result.validate( data[ i ][ j ][ k ] ) ; } } return result ; Initializing a Fallible to invalide is a very cheap operation, involving setting a boolean flag (and nothing else in my current implementations). -- James Kanze (GABI Software) email:james.kanze@gmail.com Conseils en informatique orientée objet/ Beratung in objektorientierter Datenverarbeitung 9 place Sémard, 78210 St.-Cyr-l'École, France, +33 (0)1 30 23 00 34 |
|
|
|
#5 |
|
Messages: n/a
Hébergeur: |
James Kanze wrote:
>>>> Type1 foo(Type2 value) >>>> { >>>> for(size_t i = 0; i < data.size(); ++i) >>>> for(size_t j = 0; j < data[i].size(); ++j) >>>> for(size_t k = 0; k < data[i][j].size(); ++k) >>>> if(meetsRequirements(data[i][j][k], value) >>>> return data[i][j][k]; > >>>> } > A structured solution would break this down into several > functions. How exactly would that ? The calling function would still have to check if the called function found the value or not, and end the loop if so. But of course, loops cannot be ended from inside, except with the boolean variable. So how is this a better solution? > In the next release of C++ (with lambda), you could > easily do the whole thing with a single call to std::find_if: > the predicate for std::find_if on the outer dimension would > invoke std::find_if on the next dimention, and so on. The outer dimension would still have to stop if the inner dimension found the value. How does it do that? > That is, of course, a real trap. Change the structure, and your > code silently changes meaning. Having a one level break is bad > enough. I prefer breaks over having to use extraneous useless booleans which do nothing but make the code less readable, and possibly even less efficient. It's not like breaks would stop you from doing it in a purely structured manner. They are optional. |
|
|
|
#6 |
|
Messages: n/a
Hébergeur: |
Juha Nieminen wrote:
) James Kanze wrote: )> In the next release of C++ (with lambda), you could )> easily do the whole thing with a single call to std::find_if: )> the predicate for std::find_if on the outer dimension would )> invoke std::find_if on the next dimention, and so on. ) ) The outer dimension would still have to stop if the inner dimension ) found the value. How does it do that? In a hypothetical language, where this is a 'lambda' function with single parameter, called 'a', returning the value of do_something_with: { a | do_something_with(a) } And 'or' is a shortcut operator so that a or b equals a ? a : b This finds the first result in an array: find_it = { arr, func | if (arr) { func(arr[0]) or find_it(arr[1..], func) } else { undef } } result = find_it(array, { a| meetsReqs(a) ? a : undef } ); And this is a way to find the first result in a 3-D array: result = find_it(array3, { array2 | find_it(array2, { array1 | find_it(array1, { a| meetsReqs(a) ? a : undef }) }) }); But, of course, given an is_array() function, this finds the first result in an N-dimensional array: array_find = { func, a | if (is_array(a)) { find_it(a, array_find(func)) or undef } else { func(a) or undef } } result = find_it(array, array_find(func)); NB: The result of calling a function with less arguments is a function where those arguments are filled in. For example, if plus() adds its two arguments then you can do: increase2 = plus(2); Lots of features, huh ? But languages have existed for a long time that have all of them. Some of them even resemble C. (Say, LPC) SaSW, Willem -- Disclaimer: I am in no way responsible for any of the statements made in the above text. For all I know I might be drugged or something.. No I'm not paranoid. You all think I'm paranoid, don't you ! #EOT |
|
|
|
#7 |
|
Messages: n/a
Hébergeur: |
Willem wrote:
> In a hypothetical language, where this is a 'lambda' function with single > parameter, called 'a', returning the value of do_something_with: > > { a | do_something_with(a) } > > And 'or' is a shortcut operator so that a or b equals a ? a : b > > This finds the first result in an array: > > find_it = { arr, func | > if (arr) { func(arr[0]) or find_it(arr[1..], func) } > else { undef } > } > result = find_it(array, { a| meetsReqs(a) ? a : undef } ); > > And this is a way to find the first result in a 3-D array: > > result = find_it(array3, { array2 | > find_it(array2, { array1 | > find_it(array1, { a| meetsReqs(a) ? a : undef }) > }) > }); > > But, of course, given an is_array() function, this finds the first result > in an N-dimensional array: > > array_find = { func, a | > if (is_array(a)) { find_it(a, array_find(func)) or undef } > else { func(a) or undef } > } > result = find_it(array, array_find(func)); This starts resembling Haskell. Exactly as obfuscated. ![]() |
|
|
|
#8 |
|
Messages: n/a
Hébergeur: |
Juha Nieminen wrote:
) This starts resembling Haskell. Exactly as obfuscated. ![]() Actually, I had Perl in mind when I was writing it, although I was very liberal in how I wrote stuff, so the resemblance is small. Talking about Perl, obfuscated, and functions, how about this piece of code, demonstrating ADT and data hiding: sub create_stack { my @stack; # Only accessible from within this function, right ? return ( sub { push @stack, @_ }, sub { pop @stack } ); } my ($pusher, $popper) = create_stack(); $pusher->('x'); $pusher->('y'); print $popper->(); $pusher->('z'); print $popper->(); print $popper->(); Who needs OOP ? :-) SaSW, Willem -- Disclaimer: I am in no way responsible for any of the statements made in the above text. For all I know I might be drugged or something.. No I'm not paranoid. You all think I'm paranoid, don't you ! #EOT |
|
![]() |
| Outils de la discussion | |
|
|