Experiment: functional concepts in C

  • Thread starter Ertugrul Söylemez
  • Start date
E

Ersek, Laszlo

Kaz Kylheku said:
Sure, you can always put together a portability programming
tournament, where everyone pays to enter, and the winner fetches a
monetary prize.

The same economic factors as in poker, golf, competitive road running,
etc.

In actual software, maximal portabilty is the enemy of portability.

If you are paid in the long term to bring the best out of any given
platform, then again to port the program to the next platform when your
customer changes platforms, then you are right, and I think I agreed to
that before.

OTOH, you seem to say that software developed along different incentives
is not "actual software". I disagree. If it gets the work done reliably,
it is actual software.


(I'm reordering quotes from your post.)
For instance, throughout this thread you've been advocating the
addition of /completely unnecessary/ code (bearing unnecessary risk)
to a program for the sake of portability.

For my situation that code (and the resultant risk, if any) is not
unnecessary and not without reward. I think that code will help me catch
bugs and/or, as you say, increase portability. Portability helps me
decrease the burden of maintenance and porting, and after correctness,
that's my ultimate incentive -- no maintenance. I code C first and
foremost to scratch my itches -- I'm currently hired to do different
stuff. I don't want to hack continuously on those solutions as I wander
among platforms. If somebody else finds my code useful, that's a big
additional benefit, but not the main motive.

I will sacrifice performance and features in order to deliver myself
from maintenance burden. You might implement tight loops in assembly, or
use platform-specific async IO. I will write that loop in C and call
select() and be done with it. I will reimplement functionality that has
been available in glibc for 15 years, if it's not in SUSv2. You might
come up with an elaborate subsytem of pluggable modules to collect /
mask implementation-dependent behavior. I'd rather avoid (work around)
such behavior.

If I had to ask the sysadmin to install a new version of gcc, because my
code relies on gcc extensions, or had to install gcc under $HOME myself,
that would immediately qualify as a lost cause. While in a professional,
paid-for setting, nothing should be more natural (ideally) than choosing
your tools.

Does this make my code non-actual? I don't think so. (Even though it
could obviously never compete with software written by a skilled, hired
dev team.)

I didn't say anything about less work. A lower /level/ of reasoning
doesn't translate to less work.

How can it ever be less straining to add more code, compared to not
adding it at all?

Any monkey can read a couple of ISO standards a few times over again
and learn to spot nonportability in code.

I strongly disagree. Most "monkeys" can't be bothered to read *any*
documentation at all. Even if they read tutorials and references, and
even if they have no difficulty implementing all kinds of algorithms
with procedural/imperative language constructs and a bit of dynamic
dispatch, they mostly hit rock bottom when they face "undefined
behavior" and "why doesn't it work on Solaris, it worked on Linux".

Obsessing about standards is tedious, boring and busywork, not noble or
creative in the least, but with my incentive in mind, it's of paramount
importance. (And it still requires more concentration than a "crossword
puzzle" -- I can tell, I do both.)

``Hey look, that text stream was closed and a few lines before it,
it's clear that the last character written wasn't a newline! I'm so
smart!''

Mock as much as you like, this is not done for circle-jerking purposes.

Have a nice day,
lacos
 
E

Ersek, Laszlo

Obsessing about standards is tedious, boring and busywork, not noble or
creative in the least, but with my incentive in mind, it's of paramount
importance. (And it still requires more concentration than a "crossword
puzzle" -- I can tell, I do both.)


Hmm, if you meant "solving a crossword puzzle is pattern matching, and
verifying standards conformance is equally pattern matching", then I
retract the earlier "deliberate offense" bit; I misunderstood what you
meant, sorry.

Cheers,
lacos
 
E

Ertugrul Söylemez

Willem said:
Suppose that program was one in a hundred programs that were written
without regard for free()ing every resource. Then, afterward you had
to go back and fix everything, which took ten times as long as it
would have for the original programmers. For that one program. In
total, it would have taken the original programmers ten times as long
as it took now for all those programs combined.

Wrong. If freeing resources properly is part of your natural
programming behaviour, it takes little time in most cases. However, not
doing that causes a maintainance hell, in which such bugs need to be
found first, before they can be fixed. You find, fix, text, find, fix,
text, etc. until the program shows the correct behaviour. And even if
it does, there is no guarantee that you haven't overlooked something.

Clearly doing it properly in the first place is the better choice. Bug
fixing always takes much longer than writing correct code.

And, of course, there may have been more pragmatic solutions
available, such as the extra layer of memory management mentioned
elsethread, or some kind of loop that fork()s, runs the process in the
child fork, and wait()s in the parent fork for the child to finish
before starting the next iteration.

That's neither pragmatic, nor a solution. Pragmatic would be
determining the scope of a resource and freeing it after that scope is
left. The solution is to find and fix the bugs. What you're proposing
here is just a workaround for bad program behaviour. Also it leads to
even worse maintainance hells, because now you need to manage processes
as an additional resource.


Greets
Ertugrul
 
E

Ertugrul Söylemez

Seebs said:
But it's not necessarily a programming error to let the operating
system deallocate resources. It'd be a programming error if that were
contrary to the documented behavior of the OS. Otherwise, it may well
be intentional and well-considered.

I think, if it's intentional and well-considered, the compiler should
optimize it away. The compiler is a much safer layer for such
optimizations. If the call to 'free' is placed before a 'return' in the
main function and the main function is not called recursively, just
ignore the call (if it's safe to ignore on the target platform).

I don't know to what extent compilers like GCC implement such
optimizations. Probably to none at all. The problem should be fixed at
the right place. Writing correct code should never be a problem.

On unix-like stuff, as long as they are actually *threads*, it usually
doesn't.

Well, sure, but you still have to kill the threads. The operating
system is not fault tolerant here.

I'm not convinced. There are a lot of programs which are, by design,
run EXTREMELY often.

Of course, but you don't expect those programs to allocate millions of
blocks anyway. If they do, they can't run that often anyway. =)

I do a lot of work on a build system. In the course of a single run
of the build system, there are programs that get run upwards of a
hundred thousand times. (Say, the shell.) In that case, I think it
makes a great deal of sense to think very carefully about performance
of startup and exit.

That's a good point. I agree that there can be places, where it's
appropriate not to free memory, but that shouldn't be decided by
application programmers, but by compiler programmers. Then it would use
the semantics of the operating system without having to write wrong
code.

Which is why it's useful to know what the operating system's designed
semantics are. One could easily imagine an operating system where a
particular device could only accept full-block writes of, say, 512
bytes. However, we don't then say that it is a "programming error" to
ever write to any device except in 512-byte blocks...

That's not the same thing. Not freeing memory doesn't change the
program's behaviour semantically.


Greets
Ertugrul
 
E

Ersek, Laszlo

Yes, and that place is that big comfy chair in your psychiatrist's
office.

You know, I've been here for a short time only, and also I usually don't
like to taint my name by posting expletives, but who cares.

**** you, Kaz. Shove your reputation and track record up your ass. Being
right (if at all) is no excuse for being an asshole *this much*. I would
trade colleagues or co-discussants with top 1% skillset AND arrogance
for colleagues or co-discussants with top 10% skillset AND arrogance any
time.

lacos
 
N

Nick

Seebs said:
Consistency. If I free things when I'm done with them regardless of
whether I'm done with them for this loop or for this program, my code
will be more consistent and easier to follow.

Although it can be pretty painful to do so. Consider a tree of
structures that holds an interpreted language, with tokenised
instructions, cached versions of expressions, links to lines for
conditional statements etc. And then the data structures that get
created to hold the variables when it runs. You've got a lot of
tree-walking to do at program termination for no good effect.
 
C

Chris M. Thomasson

Nick said:
Although it can be pretty painful to do so. Consider a tree of
structures that holds an interpreted language, with tokenised
instructions, cached versions of expressions, links to lines for
conditional statements etc. And then the data structures that get
created to hold the variables when it runs. You've got a lot of
tree-walking to do at program termination for no good effect.

I tend to try and setup a region allocator for a given linked
data-structure. When I want to destroy the nodes within said structure, I
just flush the region allocator. No need to walk the tree. When this
approach is applicable, it works like a charm.
 
N

Nick

G> "Nick said:
I tend to try and setup a region allocator for a given linked
data-structure. When I want to destroy the nodes within said
structure, I just flush the region allocator. No need to walk the
tree. When this approach is applicable, it works like a charm.

But unless you want to destroy the tree and carry on, this doesn't give
you much (anything?) that letting the OS do the same wouldn't give you.
A program that executes a script is likely to be in this class.
 
S

Seebs

**** you, Kaz. Shove your reputation and track record up your ass. Being
right (if at all) is no excuse for being an asshole *this much*. I would
trade colleagues or co-discussants with top 1% skillset AND arrogance
for colleagues or co-discussants with top 10% skillset AND arrogance any
time.

I'm not sure I'd phrase it that strongly, but I recently read a book
titled "The No Asshole Rule", which argued persuasively that rudeness
and hostility are not, in fact, justified by skill or expertise. I've
been thinking about that a bit, and trying to apply it more consistently
in my own Usenet activities; I'm trying to just not respond to people
if I couldn't respond courteously.

.... Note that I do not claim to have succeeded, merely that I'm making an
effort in that direction.

-s
 
S

Seebs

Although it can be pretty painful to do so. Consider a tree of
structures that holds an interpreted language, with tokenised
instructions, cached versions of expressions, links to lines for
conditional statements etc. And then the data structures that get
created to hold the variables when it runs. You've got a lot of
tree-walking to do at program termination for no good effect.

Ahh, not quite so!

See, the hard part isn't actually calling the tree-walker. It's writing
it. But I should do that in any event, because I might, say, have to parse
more than one chunk of code in the language. So, having parsed a hunk of
code, I should definitely have a way to free the whole parsed chunk of code.

And once I've got that, it's a one-line change to free the program after
executing it. And if that turns out to be too expensive, it's a one line
change not to do it.

Writing the code to correctly handle freeing the parse trees is valuable
and helps me ensure that my design is correct and robust. Omitting a single
call to it strikes me as a possibly reasonable design decision to make later
based on evaluation of the cost of the tree walking, and so on -- but I've
derived the logical benefits by writing the tree walker, which I may well
end up using even though I don't free the whole parse tree at the end of
execution.

-s
 
C

Chris M. Thomasson

Nick said:
But unless you want to destroy the tree and carry on, this doesn't give
you much (anything?) that letting the OS do the same wouldn't give you.
A program that executes a script is likely to be in this class.

Contrived example alert! Okay, let's say that you have an object which
contains a linked data-structure:
_____________________________________________________________
struct tree
{
struct tree* left;
struct tree* right;
};


struct element
{
struct tree node;
/* [payload] */
};


struct object
{
struct tree* tree;
};
_____________________________________________________________




Now, a lot of these objects get created, and start to mutate there trees.
When it's time to destroy an object I don't want to have to manually iterate
and destroy every damn element. I would much rather flush a region allocator
that I setup for it. So, I would probably try to see if the following scheme
would work for this problem:
_____________________________________________________________
struct tree
{
struct tree* left;
struct tree* right;
};


struct element
{
struct tree node;
/* [payload] */
};


struct object
{
struct tree* tree;
region_partition* part;
};


struct object*
object_create(region_allocator* ralloc)
{
region_partition* part = ralloc_partiton_create(EXPANDABLE);

if (part)
{
struct object* obj =
ralloc_partiton_alloc(part, sizeof(*obj));

if (obj)
{
/* initialize `obj' */

obj->part = part;

return obj;
}

ralloc_partiton_destroy(part);
}

return NULL;
}


struct element*
object_add(struct object* const self,
/* [payload] */)
{
struct element* elem =
ralloc_partiton_alloc(self->part, sizeof(*obj));

if (elem)
{
/* setup `elem' payload */

tree_insert(&self->tree, &elem->node, /* [payload] */);
}

return elem;
}


void
object_destroy(struct object* const self)
{
ralloc_partiton_destroy(self->part);
}
_____________________________________________________________



The `object_destroy()' is extremely simple. I don't have to manually iterate
the tree just to destroy individual element objects. Instead, I destroy the
partition that they were all allocated from. I guess you can say it's kind
of a hybrid between automatic and manual memory management. This of course
has many caveats, however when it's applicable to a problem, well, it works
great!

:^)
 
E

Ertugrul Söylemez

Kaz Kylheku said:
Yes, and that place is that big comfy chair in your psychiatrist's
office.

Is that your reasoning? Let me give you a hint: It's quite poor.

Now go play elsewhere, kiddy.


Greets
Ertugrul
 
E

Ersek, Laszlo

I recently read a book
titled "The No Asshole Rule", which argued persuasively that rudeness
and hostility are not, in fact, justified by skill or expertise.

Yep, I read about it on your blog. I had a feeling in the back of my
head that somewhere I read something relevant, in a bit more articulate
form. Now I remember.

Thanks,
lacos
 
K

Kaz Kylheku

You know, I've been here for a short time only, and also I usually don't
like to taint my name by posting expletives, but who cares.

**** you, Kaz. Shove your reputation and track record up your ass. Being
right (if at all) is no excuse for being an asshole *this much*. I would
trade colleagues or co-discussants with top 1% skillset AND arrogance
for colleagues or co-discussants with top 10% skillset AND arrogance any
time.

Hi Lacos,

We are all technically right in this debate, since we know what is going on in
the code. This is not right versus wrong, but sane versus insane.

I honestly do not believe that someone who compares operating system process
cleanup to a mother who is taking on the room-cleaning responsibilities in
place of a ``bad'' child has an entirely firm grasp on reality.

I believe that my remark above is a perfectly crafted work of Usenet art;
the culmination of years of newsgroup experience. I wouldn't change
a word of it. (In my first draft, I used ``shrink'', but I opted to
avoid the colloquialism).

You might want to examine the reasons why you reacted to it, it clearly has
found a collateral target in you also.

I don't quite follow the numerics of your reasoning about who you would rather
work with, but you should think carefully about who the real problem
personalities are. There are a hundred ways of being damaging that don't
involve overtly hostile, or abrupt behavior.

The root of the problem here is that we are discussing inanimate objects, which
serve us according to our requirements; to that extent they are ``good'' or
``bad''. Unlike a mother, the operating system is not an independent /person/
who suffers from taking on additional responsibilities for the sake of others.
The very language does not fit.
 
I

ImpalerCore

I'm not sure I'd phrase it that strongly, but I recently read a book
titled "The No Asshole Rule", which argued persuasively that rudeness
and hostility are not, in fact, justified by skill or expertise.  I've
been thinking about that a bit, and trying to apply it more consistently
in my own Usenet activities; I'm trying to just not respond to people
if I couldn't respond courteously.

... Note that I do not claim to have succeeded, merely that I'm making an
effort in that direction.

I think most often its a conflict in personality types, and don't
recognize it as such.

Choleric - Like to use strong language and conflict to resolve
problems. The details are not important to him/her. Willing to fight
for their viewpoint.
Meloncholy - Like to find every little nit-picky problem. Very
meticulous. They can be depressing cause they are complainers and
hard to satisfy.
Phlegmatic - Strong desire to avoid conflict. Tend to take a long
time to make a decision. Tend to sit in the back and avoid being
involved. They are quiet and suddenly blow-up to release steam.
Sanguine - Haven't personally known a software guy with this
personality yet.

The all-out flame wars are typically when 2 cholerics clash head to
head. As a phleg myself, if someone chewed me out on the forums, I
would instinctually avoid responding to the person.

Best regards,
John D.
 
S

Seebs

I believe that my remark above is a perfectly crafted work of Usenet art;
the culmination of years of newsgroup experience. I wouldn't change
a word of it. (In my first draft, I used ``shrink'', but I opted to
avoid the colloquialism).

I don't think I agree. Insulting the previous poster is not likely to
yield positive results. While it is quite possible that reasoned
argumentation won't either, the simplest course would be to omit the insult
entirely, since it doesn't really advance discussion.

I happen to agree more with you than with the person you flamed, but I don't
feel that the flame really improved the tone of the group.

I used to flame people a lot more. I stopped, and I have found that I get
better results now.

-s
 

Ask a Question

Want to reply to this thread or ask your own question?

You'll need to choose a username for the site, which only take a couple of moments. After that, you can post your question and our members will help you out.

Ask a Question

Members online

No members online now.

Forum statistics

Threads
474,432
Messages
2,571,682
Members
48,796
Latest member
Greg L.

Latest Threads

Top