They are onerous to me. I have a list-processing library that uses
map-like functions to do all sorts of things, including output.
I'd need to see the language to see how easy it is to recast in the
system. There's also the question of whether the language is
fundamentally misdesigned, or designed on an equally good but
incompatible principle.
Now, if you are being C-specific, I agree that such patterns are not
common, but I got the feeling you were talking more generally than that.
In other languages, arbitrarily restricting function arguments is a
disaster. (But I see from what follows that the idea is, essentially,
C-based so programmers in other languages need not take this advice.)
I'm essentially talking in terms of C-like languages.
Ah, so this is a C-specific. I'll stop talking about programming more
generally then.
"Can be written in pure portable C" doesn't mean "cannot be written in
other languages".
What's more, there are almost no bit-shuffling functions Simply
writing
int max(int a, int b) { return a > b ? a : b; }
int max3(int a, int b, int c) { return max(max(a, b), c); }
stops max3 from being a bit-shuffler. That just seems way too
restrictive. At the same time, the very vague words "specification" and
"bit state" make it hard to see what else is excluded.
Same mistake. "Can be written without subroutines" doesn't mean "cannot
be written with subroutines".
we can write max3
int max3(int a, int b, int c)
{
if(a > b && a > c)
return a;
if(b > c)
return b;
return b;
}
Then we can drop that version into any program that calls max3. So if we
can't find the source to max2(), or can't figure out how it works because
no-one has told us about that weird syntax, all is not lost.
Now
void hello
{
printf("hello world");
}
cannot be rewritten without any external subroutines. It does't have that
characteristic.
AH!! You are not even sticking to your own made-up terms. I took
"subroutine" to be some general term, but you mean one of your
"procedures" or "IO procedures", yes? Banning subroutine calls in the
general sense makes no sense.
Yes it does. Leaf functions are more useful than non-leaf functions
because they can be cut and pasted, because their behaviour can be
determined by examining them. However we're not banning subroutines.
We're retaining the right to ban subroutines should be wish to do so.
It's possible (and in my view more useful) to extend this rule to all C
functions. That means taking a suitably abstract view of the
specification. This is the single most important reason why I don't
think this sort of classification is useful. I want to be able to
reason (often about whole programs) written in portable C, regardless of
function arguments and "IO".
You're right in a very non-trivial sense. IO procedures like fopen()
are widely portable, they can be implemented on lots of physically
very different devices. So ANSI standard C tries to make code portable
by specifying a standard interface for the task of reading and writing
streams for an external datasource.
What I am saying is that this approach doesn't work. Because you can
always get some physical device which won;t support your interface.
Call to streams which pass over internet connections can't sensibly be
blocking, because internet connections hang, and users want some
sort of feedback. So stdio doesn't work. Everything breaks that relies
upon it.
Now you can easily say "OK, pass fgetc() a timeout callback". But that
too will break when the next technology comes along.
even this
void hello(char *str)
{
strcpy(str, "hello world");
}
is not portable. It will break on a Microsoft compiler in default mode.
So we need to be able to retain the right to ban subroutines.
We can't ban malloc() unfortunately. That's the one subroutine we really
can't do without.
For example, I'd like to be able to argue about portable threaded C
programs and not a single function that uses any of C's concurrency
primitives is bit-shuffler. I'd rather develop ways to argue about a
much larger class of functions, than to home in on a tiny set.
I'd like to extend this analysis to parallel programming. But let's
get everything straight for serial programs first.
The same is true of ones that violate your rules, and that, to me, is
more important than your seemingly arbitrary classification.
No, if a function must "clock out bits on the rising edge", you can't
test that that is working by writing C on a Unix box. At least in any
way that I know.
Still baffled. How can you test the above independently of the callback?
The function sums n results from calls to the callback. So we black-box
test it with various callbacks that return different numbers, and show
that it's returning correct results for all combinations of n and
functions with different characteristics. I put "prove correct" in
scare quotes because of course that only proves it correct in an
engineering sense. But we know it's been written by someone who
wouldn't introduce any deliberate perversities, it sums the results
of n calls to cb().
Now we ship it, and our customer uses it with his own callback, which
is highly intricate and we couldn't develop ourselves. And we know it
will sum n calls.
But callbacks are special. You've lost a degree of safety
Eh? You've changed the meaning entirely independently of what rules the
callback obeys.
A loop of n calls to a callback can be written
for(i=0;i<n;i++)
x += cb();
or
while(n--)
x += cb();
They're both correct and interchangeable, unless cb is allowed to modify
i or n. That's what you've got to ban.
It's key for me. Feel free to ignore the detailed comments (I'm not
sure I understand the details of what you are saying anyway). The big
objection is that you focus the idea of reasoning about code on too
small a set of functions (in the C sense of the word), and with too
low-level a view of what is being verified by the proof or argument.
No, I give simple examples, but of course the method is entirely
pointless if applied to trivial functions. What matters is how it
scales up to massive programs which are too complex for any one
person to develop independently.
And Haskell? It's functions are about as close to mathematical
functions as it is possible to get, but Haskell programs do IO.
I'm not a mathematician. But I understand that
x + 2
is a function of x.
x + a value obtained from somewhere
is not a function of x, we can't analyse it or say anything about it.
But people here are not "making too much" of how the word function is
used. This is C group, and function has a meaning that is too well-know
to be hijacked for your purposes. You will just cause confusion.
"Malcolm-function" is flattering, but I can hardly claim to deserve to
have the concept of a mathematical function defined by discrete bits
named after me. "Bit-shuffling function" seems to cause as much confusion
as "function". But feel free to suggest a term.
I don't see what you mean. I can replace your code with
puts("H\bHello world")
and it succeeds and fails in the same way. If the first meets your
specification (and I don't know how it could, but that's another matter)
the second does as well.
You don't know that.
lets say we run both functions on identical machines, with the following code.
if(fork())
printhelloworld();
else
closedowntty();
so one thread is priting out the message, the other shuts down the tty
whilst it is executing. can we now guarantee indentical output for both
versions of printhelloworld() ?
That's not an advantage, or, if it is, I can't see what advantage you
are claiming. It just seems to be another edict about what programmer
should do.
Because we can take the function which converts an ascii string to
a real, and use it in other contexts. But only if it is a bit-shuffling
function, not if it is tied to reading characters from IO.[ IO procedure programming different from bit shuffling ]
That's not an advantage either.
There's no particular reason why someone who is a good at bit shuffling
should also be good at IO, or vice versa, other than the general
observation that people who are good at one task are usually at least
competent at a related one.
The fields have a different logic, they require a different mindset,
they require different physical resources. The software assets produced
have a different lifespan and applicability. We'll probably find they are
best handled in different languages.