|
|
| Author |
Message |
| 13 new of 45 responses total. |
albaugh
|
|
response 33 of 45:
|
Jan 31 20:44 UTC 2007 |
Re: error detection and goto's, having programmed a lot in a language without
goto's (Pascal), I had to solve this by making an error state something
checked for in loops, so as to allow for an early and orderly exit out of a
loop. I think that was better than trying to use or rely on being able to
just jump out to somewhere when the heat got turned up.
|
cross
|
|
response 34 of 45:
|
Jan 31 21:16 UTC 2007 |
I've found that that was a far worse way to go: you ended up with much more
complex code for not a lot of benefit. Particularly when you were in
situations where you successively built up a number of resources that had to
be checked for and deleted along the way, not necessarily using loops, just
if's and the like. It was too easy to forget to release something somewhere
along the way. For instance, consider this code:
int
getresources(void)
{
char *a, *b, *c, *d;
Lock *o, *v, *w;
a = malloc(SOMETHING);
if (a == NULL)
return FAILURE;
o = acquire(some_lock);
if (o == NULL) { /* Ie, we failed to get the lock.... */
free(a);
return FAILURE;
}
b = malloc(SOMETHING_ELSE);
if (b == NULL) {
release(o);
free(a);
return FAILURE;
}
v = acquire(some_other_lock);
if (v == NULL) {
free(b);
release(o);
free(a);
return FAILURE;
}
c = malloc(ANOTHER_THING);
if (c == NULL) {
release(v);
free(b);
release(o);
free(a)
return FAILURE;
}
w = acquire(a_third_lock);
if (w == NULL) {
free(c);
release(v);
free(b);
release(o);
free(a)
return FAILURE;
}
d = malloc(THE_FINAL_THING);
if (d == NULL) {
release(w);
free(c);
release(v);
free(b);
release(o);
free(a)
return FAILURE;
}
/* Do something with the resources we got.... */
return SUCCESS;
}
Well, what if I have to add another resource in there? Then I need to be
sure that I release it everywhere appropriate...it's easy to forget something,
and this is just a contrived example!
Okay, you say, you could just nest the if's. Let's see what the code looks
like:
int
getresources(void)
{
char *a, *b, *c, *d;
Lock *o, *v, *w;
a = malloc(SOMETHING);
if (a != NULL) {
o = acquire(some_lock);
if (o != NULL) {
b = malloc(SOMETHING_ELSE);
if (b != NULL) {
v = acquire(some_other_lock);
if (v != NULL) {
c = malloc(ANOTHER_THING);
if (c != NULL) {
w = acquire(a_third_lock);
if (w != NULL) {
d =
malloc(THE_FINAL_THING);
if (d != NULL) {
/* Do something
with our
acquired
resources... */
free(d);
return
SUCCESS;
}
release(w);
}
free(c);
}
release(v);
}
free(b);
}
release(o);
}
free(a);
}
return FAILURE;
}
Well, we don't repeat the code to release the resources, which is good,
but man is that ugly. In particular, the (presumably important) code is
obscured because it's indented almost the entire way to the right! I
really don't like that. Of course, we could split it off into another
routine or something, but still.... And I guess one could argue that
visually, the code sort of `funnels' the programmer into the important
segment, but I'd rather have the main line of code flow vertically down
the page. Here's the solution with goto's emulating exceptions:
#define throw goto /* Maybe this will make the purists feel better... */
int
getresources(void)
{
char *a, *b, *c, *d;
Lock *o, *v, *w;
b = c = d = o = v = w = NULL;
a = malloc(SOMETHING);
if (a == NULL)
throw exception;
o = acquire(some_lock);
if (o == NULL)
throw exception;
b = malloc(SOMETHING_ELSE);
if (b == NULL)
throw exception;
v = acquire(some_other_lock)
if (v == NULL)
throw exception;
c = malloc(ANOTHER_THING);
if (c == NULL)
throw exception;
w = acquire(a_third_lock);
if (w == NULL)
throw exception;
d = malloc(THE_FINAL_THING);
if (d == NULL)
throw exception;
/* Do something useful with our resources.... */
return SUCCESS;
exception:
if (d != NULL) free(d); /* Can't happen, but why not? */
if (w != NULL) release(w);
if (c != NULL) free(c);
if (v != NULL) release(v);
if (b != NULL) free(b);
if (o != NULL) release(o);
if (a != NULL) free(a);
return FAILURE;
}
Here, the benefit is that the main logic (which would go where the, ``Do
something useful with our resources.....'' comment is) isn't obscured all
the way over to the right side; there is a slight risk in that the
programmer could theoretically not add a test in the exception handling code
to remove a new resource, but they might forget to free it in the code that
creates it, too. The only real guarantee that you get with the nested `if'
version is that resources are definitely released in reverse order of
allocation. But I think the additional risk is worth the extra readability.
I guess you could just reformat the nested if code so that everything is
sensibly indented, and all the closing braces are on one line or something,
but I think the exception model is better, and in newer languages, you might
get ordering guaranteed by the underlying implementation.
|
gull
|
|
response 35 of 45:
|
Feb 3 21:37 UTC 2007 |
Re resp:33: Delphi got around that problem by adding exception handling
to its Pascal-based language. (And goto, but you rarely needed it for
anything.)
|
remmers
|
|
response 36 of 45:
|
Feb 5 13:27 UTC 2007 |
Re #32 re #31: I wouldn't presume to defend my one-liner on grounds of
readability or maintainability, of course. For that, I'd recast it the
way it was done in #30, or live a little more on the edge and do:
i = 0;
p = s[0];
while (*p)
if (isspace(*p))
++p;
else if ((++i) < m)
p = s[i];
else
break;
/* If i < m, we have the first all-blank row, else there isn't one */
/* Look Ma, no curly braces! */
But they're all the same solution, really -- except for some language-
specific tweaking -- in that they use the same two variables
initialized, updated, and tested in exactly the same ways. The
predicate-logic based approach to algorithm developement pioneered by
Dijkstra does tend to lead to economical, maintainable solutions, I've
found. Once you have such a solution, *then* you can concern yourself
with expressing it in a style appropriate to the programming language at
hand. As David Gries put it, you should program *into* a language, not
*in* a language.
|
cross
|
|
response 37 of 45:
|
Feb 7 18:59 UTC 2007 |
Regarding #31; I realized that. #32 was a joke. That said, my question still
stands: Dijkstra was working in an environment where the primitive tools
consisted of loops, conditions, etc. We now have a richer vocabulary in a
variety of languages; does that change our cognitive approach to the problem?
That is, how does one's expressive capability - independent of specific
language - affect the way in which one applies the predicate-logic based
approach?
|
remmers
|
|
response 38 of 45:
|
Feb 7 22:30 UTC 2007 |
Of course #32 was a joke. I even laughed. But the question you raise
in #37 is interesting. I haven't thought about it all that much, but I
imagine that if the language departs significantly from the "Von Neumann
machine" model - e.g. a functional programming language - it probably
would.
However, doesn't programming in most of today's widely-used languages
ultimately come down to manipulating variables with loops, conditionals,
etc? I'm thinking of languages like Java, JavaScript, Perl, Python,
Ruby. For those, I'd think the predicate-logic approach would be pretty
much the same.
I'll pose the following exercise:
foreach language in (Perl, Java, JavaScript, Ruby, Python, etc)
{
Rephrase the "first all-blank string" problem in a form
sensible for that language;
Solve it;
}
(Rephrasing is necessary since different languages have different
representations and operators for strings.)
|
cross
|
|
response 39 of 45:
|
Feb 8 03:26 UTC 2007 |
Regarding #38; (I know that you know that I know that you knew that I was
joking... :-)).
I have solutions for Perl, Python, and Ruby. But I'm going to hold off on
posting them for a few days to give other folks a chance (I've been
responding too much too quickly; where's the fun in that? A conversation
between two people is boring at best).
As for the primitives we employ, I'd say no, at least not at the level the
programmer should be concerned about. Today, in a number of languages,
regular expressions are now first class objects, as are exceptions,
classes with behavior that can be modified at runtime, etc. Our vocabulary
of constructs has greatly expanded, and we're working at a level far above
that first projected by JvN etc. I'd say that the method can still work,
but we've got to recognize that we can add new primitives such as, ``matches
this pattern'' or ``responds to this method'' to our set.
|
cross
|
|
response 40 of 45:
|
Feb 11 21:14 UTC 2007 |
Okay, no one has responded in the last five days, so here are my solutions
to Remmers's puzzle for Ruby, Python and Perl. Critique away.
# -+=Ruby=+-
def find1stblank(strings)
strings.detect { |str| str =~ /^\s+$/ }
end
strings = [ "this", " is ", "a", " ", "string" ]
p find1stblank(strings)
strings = [ "this", " is ", "a", "string" ]
p find1stblank(strings)
# -+=Python=+-
def find1stblank(strings):
for i in range(1, len(strings)):
if strings[i].isspace():
return i
return -1
strings = [ 'This is a test', ' ', 'hi there' ]
print(find1stblank(strings))
strings = [ 'This ', 'is ', 'another ', 'test', ' ' ]
print(find1stblank(strings))
strings = [ 'This ', 'is ', 'another ', 'test' ]
print(find1stblank(strings))
# -+=Perl=+-
use warnings;
use strict;
sub find1stblank
{
for (my $i = 0; $i < @_; $i++) {
return $i if $_[$i] =~ /^\s+$/;
}
return -1;
}
my @lines = ("This", "is ", " ", "a", "test");
print find1stblank(@lines), "\n";
@lines = ("This", "is ", "another ", "test", " ");
print find1stblank(@lines), "\n";
@lines = ("This", "is ", " not ", "a", "test");
print find1stblank(@lines), "\n";
Note that for the Ruby code, I take a more Ruby-ish approach, returning a
reference to the row in question instead of just an index.
Note also that these solutions take advantage of facilities of the language
that are beyond that of our more primitive examples (e.g., in Ruby and Perl,
we have regular expressions as a basic idea, whereas in Python we can ask the
string whether it's blank).
Two related questions: is taking advantage of these language features too much
of, ``programming in a language'' instead of ``programming into a language''?
That is, can we legitimately frame the question in terms of regular
expressions and objects at a very high level, or is that ``cheating''? The
second question, even if it *is* cheating, do we care? Should we?
|
cross
|
|
response 41 of 45:
|
Feb 14 23:02 UTC 2007 |
Another day, and no additional posts. Okay, here's the Java version:
/**
* Test of something for John Remmers.
*/
public class Find1stBlank {
/**
* Find the first all-blank string in the passed regular expression.
*/
public static int Find1stBlank(String[] strings) {
for (int i = 0; i < strings.length; i++)
if (strings[i].matches("^\\s+$"))
return(i);
return(-1);
}
/**
* Main program entry point.
*/
public static void main(String[] args) {
String[] strings = { "This ", " is", "a", "string" };
System.out.println("Find1stBlank == " + Find1stBlank(strings));
String[] strs = { " But this ", " is ", " ", " a blank." };
System.out.println("Find1stBlank == " + Find1stBlank(strs));
}
}
|
remmers
|
|
response 42 of 45:
|
Apr 8 11:31 UTC 2007 |
Responses #40 and #41 illustrate the effect of language primitives (in
this case, regular expressions) on the shape of a solution.
(Aside: This item has showed up as a shared bookmark on del.icio.us.
Have a look at http://del.icio.us/tag/grex/ and
http://del.icio.us/url/93a9640a15e68aef7b1d7837f006cc01 ...)
|
yecril71pl
|
|
response 43 of 45:
|
Dec 3 13:23 UTC 2007 |
How is it that nobody has come up with a solution that uses "come from"?
|
nharmon
|
|
response 44 of 45:
|
Dec 3 13:51 UTC 2007 |
I've never liked "come from" because it can really confuse you when
you're debugging something. Like, you'll be wondering why a certain line
was never executed only to find out later that it was because the line
before it was associated with a "come from" much later on in the program.
|
mkay
|
|
response 45 of 45:
|
Apr 23 10:55 UTC 2013 |
Here is my C solution, an example for good readability :D :
int i = 0, j = 0;
while ((j = (j + 1) * !matrix[i][j]) < N && (i += !!matrix[i][j]) < N)
;
if (i < N)
printf("First all-zero row is %d\n", i);
|