Good practice of using void function return type?


M

Malcolm McLean

On 15/05/14 20:15, Malcolm McLean wrote:

This is a C newsgroup - we talk mostly about C, or related topics. So
when you use the word "function", everyone here understands it to mean
"C function", unless the context makes it clear (such as "the function
of this software is to annoy the user as efficiently as possible"). If
you want to talk specifically about functions that produce results
dependent solely on their inputs, and have no side-effects, then these
are commonly referred to as "pure functions".
Sure, I'm not the person trying to stop people using the term "function"
in the C standard context.
I would like to grab the term "pure function", but unfortunately it already
has a very specific meaning, which is

double square_root(double x)
{
double answer;
/* run a root finder of some sort */
return answer;
}

is a pure function.

extern in errno;

double square_root(double x)
{
double answer;
if(x < 0)
{
errno = -1;
return nan();
}
/* run root finder */
return answer;
}

is not, because it has an external effect.

So why not use "pure function" in a different context? You could do
that, but that's inventing or imposing a new meaning, as opposed to
picking one of the existing, generally accepted meanings of "function"
and making it clear that you're using the word with that meaning.
 
Ad

Advertisements

D

David Brown

Sure, I'm not the person trying to stop people using the term "function"
in the C standard context.
I would like to grab the term "pure function", but unfortunately it already
has a very specific meaning, which is

double square_root(double x)
{
double answer;
/* run a root finder of some sort */
return answer;
}

is a pure function.

extern in errno;

double square_root(double x)
{
double answer;
if(x < 0)
{
errno = -1;
return nan();
}
/* run root finder */
return answer;
}

is not, because it has an external effect.

That is correct.

But in programming (C or otherwise), it is useful to distinguish between
"pure functions" and "non-pure functions", because "pure functions" can
be optimised and analysed in ways that you cannot do with non-pure
functions. (This is a major reason why functional programming
languages, which put great emphasis on pure functions, are much more
amenable to formal correctness analysis.) There is /no/ useful
distinction in programming between "functions/procedures that do I/O"
and "functions/procedures that don't do I/O" - because there is no
distinct definition of I/O.
So why not use "pure function" in a different context? You could do
that, but that's inventing or imposing a new meaning, as opposed to
picking one of the existing, generally accepted meanings of "function"
and making it clear that you're using the word with that meaning.

Well, you already seem perfectly happy to pick your own meanings for
existing terms.

But I am not advocating using "pure function" to mean anything but its
standard computer science meaning (except perhaps in the context of gcc
function attributes, where the attribute "pure" declares an almost-pure
function, and the attribute "const" declares a /really/ pure function).
 
B

BartC

But I am not advocating using "pure function" to mean anything but its
standard computer science meaning (except perhaps in the context of gcc
function attributes, where the attribute "pure" declares an almost-pure
function, and the attribute "const" declares a /really/ pure function).

I thought you were in favour of leaving a compiler to get on with it instead
of giving it optimisation hints?

(Because, apart from introducing portability issues, it can cause a problem
when editing changes the function's pureness but the attribute is not
updated.)
 
D

David Brown

I thought you were in favour of leaving a compiler to get on with it
instead of giving it optimisation hints?

I like Zen here - not too much, and not too little. So sometimes my
arguments will vary, depending on the other posters. Maybe I'm just
feeling argumentative. (Write a post entitled "the sky is blue", and
see if I take the bait...)

In this particular case, I will sometimes mark functions with the gcc
"pure" or "const" attribute. It gives the compiler more information -
it is not directing its optimisations, but merely allowing them. And it
also gives the compiler extra information for compile-time checks to
enforce the pureness or constness. (I don't know if gcc actually
implements such checks, but it could do.)
(Because, apart from introducing portability issues, it can cause a
problem when editing changes the function's pureness but the attribute
is not updated.)

True enough. It is not something I would use on a long or complex
function - it should be pretty clear that the implementation is "pure"
or "const". And it is mostly used on functions where it is obvious that
the implementation should be "pure" or "const", such as mathematical
functions.
 
D

David Brown

A compiler can't use natural language. So I've given a crisp,
unambiguous definition of "function" and "procedure" which can
coded and enforced automatically. A function can only shuffle bits
about in the computer's memory, it can't do IO. (So it can't read
any bits, for example, which might be affected by an external
device in the course of execution, the state of the program on
function exit is always completely defined by the state on function
entry). A procedure can do IO. So a procedure may call a function
or another procedure, but a function may not call a procedure.

But those are not "crisp" or "unambiguous" - at best, they are rough
statements of intent for your "Malcolm-functions" and "Malcolm-procedures".

What happens when your Malcolm-function "shuffles a bit" that triggers
an I/O operation? "int foo(int *p) { int x = *p; *p = 1; return x;}"
looks like it is just "shuffling bits". But if called with "p" equal to
0 it might cause a fault leading to a screen output of an error message,
or a core dump causing disk activity, or nasal daemons which are
certainly input/output. What happens if it triggers a swap file
movement - that's I/O ? There are endless ways for Malcolm-functions to
cause I/O.

And of course the opposite is also true - a "Malcolm-procedure" might
not cause I/O depending on its inputs, or on the system as a whole. The
"Malcolm-procedure" might contain a printf, but if the program is called
with the output redirected to /dev/null then there is no output.


There are good reasons why Malcolm-functions and Malcolm-procedures are
not standard terms.
 
M

Malcolm McLean

On 16/05/14 00:48, Malcolm McLean wrote:


What happens when your Malcolm-function "shuffles a bit" that triggers
an I/O operation? "int foo(int *p) { int x = *p; *p = 1; return x;}"
looks like it is just "shuffling bits". But if called with "p" equal to
0 it might cause a fault leading to a screen output of an error message,
That has to be disallowed.
Passing volatile memory, where reading and writing has side effects that
might affect program state in ways defined by the external environment
mean that it's not a function. There's not a one to one mapping of bit
state on function entry to bit state on function exit.

You can make Malcolm-functions into "pure functions" by disallowing
pointers and globals. But there's no easy way of enforcing that in C that
doesn't radically change the language.
 
Ad

Advertisements

K

Keith Thompson

Malcolm McLean said:
That has to be disallowed.
Passing volatile memory, where reading and writing has side effects that
might affect program state in ways defined by the external environment
mean that it's not a function.

No, it means it's not a "Malcolm-function".
 
B

Ben Bacarisse

Malcolm McLean said:
You can make Malcolm-functions into "pure functions" by disallowing
pointers and globals.

Let me see if I can do this one...

"Global" must mean something that more detail-oriented, drone
thinkers might call "static storage duration", otherwise

int f(void) { static int i; return i++; }

would be a pure function. And, presumably, "disallowing pointers and
globals" is to be interpreted as applying, recursively, to any called
functions. Calling non-functions (Malcolm-procedures) is covered
because all IO implicitly uses pointers or globals, yes?.

(More muddled thinkers might have tacked on "and only calls other pure
functions".)

Also there can't be any volatile-qualified objects with peculiar
semantics either, but these functions are either "marginal" cases or
such accesses count as IO.

<snip>
 
M

Malcolm McLean

would be a pure function. And, presumably, "disallowing pointers and
globals" is to be interpreted as applying, recursively, to any called
functions. Calling non-functions (Malcolm-procedures) is covered
because all IO implicitly uses pointers or globals, yes?.
Yes, often IO is implemented by reading a global whose state is
influenced by an external device. So it's in the normal address space
of the machine and C will allow you to read it via a pointer or even
a direct hardcoded variable name.
But if you try to pass such memory to a function (a Malcolm-function
if you insist), without locking it somehow, then the function is no
longer a Malcolm-function.

int foo()
{
static int x =0;
return x++;
}

is a good example of a Malcolm-function which isn't a "pure function". The
bit state of the computer on function exit is completely determined by the
bit state on function entry, so it's a bit-shuffling function.
 
K

Keith Thompson

JohnNapier said:
Actually, the number of characters printed by printf is useful in
determining when to send a newline to the console. Such as while
printing a set of numbers. As an example I'd like to post the
following code.

for( index = 2; index < n; index++ )
{
if( isprime( index ) )
ccount = printf( "%u, ", index );

This should be:

ccount += printf( "%u, ", index );

(Presumably you've initialized ccount to 0.)
 
M

Malcolm McLean

Let me see if I can do this one...


"Global" must mean something that more detail-oriented, drone
thinkers might call "static storage duration", otherwise

int f(void) { static int i; return i++; }


would be a pure function. And, presumably, "disallowing pointers and
globals" is to be interpreted as applying, recursively, to any called
functions. Calling non-functions (Malcolm-procedures) is covered
because all IO implicitly uses pointers or globals, yes?.

(More muddled thinkers might have tacked on "and only calls other pure
functions".)
Yes, you're in a muddle if you tack this on. Probably my fault, but
still a muddle.

A Malcolm-function can't call a Malcolm-procedure. But it can access
a global, it can write to and from memory address passed to it from
pointers. So whilst pure functions are always Malcolm-functions,
Malcolm-functions aren't always pure functions. If we say what extra
restrictions we need to apply to Malcolm-functions to make them into
pure functions, we don't need to specify, "don't call Malcolm-procedures",
because a Malcolm-function already never calls a Malcolm-procedure.

It's true that we have to disallow pointers and globals for all
Malcolm-functions in the call tree to make a Malcolm-function into
a pure function.
 
Ad

Advertisements

K

Kenny McCormack

Richard said:
And you felt that Ben needed advising on checking a return value? His
programming prowess receives a +10 Skill rating. Sheesh. Do stop and
think before lecturing the bleeding obvious. What a waste of a brain.

Kiki *has* a brain? I thought it had been established that all he was was
a walking compiler and/or walking incarnation of the C standards.
 
Ad

Advertisements

M

Malcolm McLean

I don't think it's unambiguous. There seem to be several different
distinctions being made which are almost independent of each other.

There's the function/procedure distinction in the Pascal sense: A
function returns a value via the return value of the function. A
procedure doesn't have a return value. Either of these can return
values through arguments that are non-const pointer types. That's
pretty unambiguous but when writing in C, I'm not sure the distinction
is useful. Both can pass back information (status or others). Both
can change external state.
If you say that a function can only return information via a "return"
statement, then functions that dont return values (procedures) must do IO,
in fact output, or else what are they doing?
But very few languages actually enforce that. Most allow writing to
globals, or to pointers.
There's the pure/non-pure distinction: a "pure" function generates
output that depends only on its arguments, and has no state, and a
"non-pure" funcedure may reference external variables or internal
static state that changes. A "pure" function call with arguments
known at compile time could be replaced at compile-time with its
resulting value.
So we have the distinction between pure and impure functions, I'm
making a slightly different one. Hence I can't use the term "pure
function" without being highly confusing.
A Malcom-function
Exactly. Also a mathematical function, which is a mapping of an input
set to an output set.
Those who think that shuffling bits isn't I/O have never gotten
errors like "panic: I/O error in swap", "Uncorrectable ECC error
in main memory", and various complaints about CPU cache malfunctions.
Granted, these problems are rare, and if they're not, you get the
system repaired or junk it and replace it.
You have to assume that the abstract machine is functioning correctly.
If occasionally the act of writing a byte will raise an error which
needs to be handled, the system breaks down.
Now suppose we have one of those Atmel processors that can have
external RAM attached. It has internal registers (often called I/O
registers in the documentation) to control interrupts and timers,
and contain things like the current timer count, the timer count
at which to cause an interrupt, and bits to choose the speed of
the clock that drives it. These are internal to the CPU, so they
are bit-shuffling. It also has RAM which is external to the CPU,
so using RAM must be I/O. Oh, and I haven't specified where it's
fetching the code from, but it's often internal flash.
You need the abstract model of the machine which can write to
memory, which can;t be changed by any external factor. Obviously
for any physical machine, it will be physically possible to
manipulate the bits during function execution. Then the analysis
breaks down.
A Malcom funcedure (we haven't figured out whether it's a Malcom-function
or a Malcom-procedure yet) whose function is to set up a timeout
is just shuffling bits to load the I/O registers, so it's a
Malcom-function. Oh, wait -- if it needs to use RAM (such as for
auto variables), that's external, so it's a Malcom-procedure.
No, because writing to bytes with the intention of creating a
side-effect is doing IO. If we refactor the function so that
program bit state on exit is the same given bit state on entry,
but don't write to the memory-mapped bits, then that doesn't change
a Malcolm-function. It does potentially change a Malcolm-procedure.
A procedure is hardware-dependent, we only know its working
correctly if we have something connected to the memory-mapped
bytes.
However, I may not be able to tell the difference here, so I have
to look at the generated code: if it uses temporary auto variables,
it's a Malcom-procedure, otherwise it's a Malcom-function, and you
can't tell the difference looking at the source code. I guess if
the code is fetched from a flash chip external to the CPU, then
*EVERYTHING* is a Malcom-procedure. I'm not sure the distinction
is worthwhile if it depends on the optimization level of the compiler.
Everything is a procedure if the abstract machine model of reliable,
addressable memory doesn't hold, yes.
A "pure" function can't read global state that someone else changes.
The output depends only on the input.
A pure function is a Malcolm function, assuming it doesn't do any
output. But not all Malcolm functions are pure functions.
Mathematics isn't so cooperative here. Mathematical functions can
have inputs or outputs that are undefined in spots, such as
implementations of the tangent and arctangent functions. The same
applies to square root if you're expecting it to return a real
number rather than complex.
A computer can't express concepts like "every number from +/-
infinity". It can only shuffle discrete bits. So not every mathematical
function can be coded as a Malcolm-function, though most
mathematics that people want practically can.
Most of the time when that kind of distinction is made, it's called
"pure" or "impure". But it doesn't matter where the "pure" function
has state, "internal" (e.g. static variables), or "external" (e.g.
global variables or disk files), a "pure" function can't have state
that anyone modifies.
Yes, we're making a slightly different distinction than the pure/impure
function distinction.
 

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

Top