[comp.theory] Why not multiple out parameters? again

vidynath@function.mps.ohio-state.edu (Vidhyanath K. Rao) (08/29/90)

[Here kitty kitty]

A while back I posted an article asking "Why are multiple out paramters
bad?". A disk crash in my home computer together with down time on the
mail server has delayed posting a summary of the responses I got.

Most of the responses I got kept refering to "VAR" parameters leading to
aliases. To me this a totally different problem: "VAR" parameters are
implemented using pointers and pointers lead to aliases. Using "copy out"
and "copy in, copy out" parameters should avoid aliasing. The only "better
known" language [that I also know] that does this, albeit only for simple
types, is Ada. I do not have access to Ada compliers or the lastest
documentation. But Ada-80 reference manual confirms my statement above.

Let me elaborate a little on kind of things one does with functions in
mathematics but require contortions in Modula-2 or C: I can form functions
$$f: D_1 \times \cdots \times D_n \to C_1 \times \cdots \times C_m$$
and compose with "meta-functions": projections, "concantations" [products
in the sense of category theory] and permutations [and I don't have to
invent a special unique name for each codomain I use].

Thus one would have a construct of the form:
    DECLARE PROC f( p_1, ..., p_n; RETURNS r_1, ..., r_m);
Space for the r_i's would be allocated on the stack. After the called
procedure returns, these would be >*COPIED*< to the actual out paramters.
Projections can be accommadated by allowing the omission of one or more
of the result parameters in the call. Permutations and concantanations would
require list processing at least at compile time; one can do without
them if the compiler used does reasonable optimization and allows arbitarily
long [and multi-line] macros and, of course, variable declarations at the
start of any block. Given only this much functionality, aliases cannot
occur. [Of course you can use the same variable for two of the result
paramters. But the callee would not go haywire; you would be doing a
projection in an implementation dependent manner :-)]

Of course some extensions may be desirable for efficiency: InOut parameters
[using copy in, copy out] for parameters that are modified, passing pointers
to arrays and large structures rather than copying etc. [In this case,
every one would know that pointers are being used instead of the safer
alternatives, and be on guard for problems.]

The forgoing discussion should make it clear that aliasing is due to the
use of pointers in the guise of "VAR" parameters, and has nothing to do
with having multiple out parameters. So the question still remains
unanswered: Why are multiple out parameters bad?

Most of the responses referred to aliasing when passing pointers. I will
omit them.

There were two other respones worthy of note:

Frank Silbermann <fs@rex.cs.tulane.edu>:
[To my question, by E-Mail, on why returning a record of a named type,
declared just for this purpose is ok, but not multiple out parameters?]
    This has nothing to do with returning a record or other complex complex
    object. He is talking about functions with side effects, e.g. more than
    one VAR parameter. i.e. more than one "thing" gets changed. A record
    counts as one "thing" because it can be referenced by a single name.

To me this smacks of name magic. And in any case, out parameters need not
be initiliazied beforehand. They are in fact being initiazied, NOT BEING
CHANGED. It looks that way simply becuase of lacunae in certain languages.

mark@parc.xerox.com (Mark Weiser) writes:
[in response to some follow-up article posted to the board]
    Modula-(2 and 3), Cedar, Mesa, lots of others have record returns.
    I truly don't understand the comment about a record being inadequate
    because of the drastically different types--a record is that data
    structure one uses when one has drastically different types.  C requires
    that to return a record one must separately define it.  Cedar (e.g.)
    has the convention that the procedure declaration defines an implicit
    record type to be the return value...

I am not familiar with Cedar [or Mesa]. I would appreciate a list of easily
available references to such languages. IMHO, just grafting implicit record
types to Modula-2 or C will not allow efficient ways of doing projections,
concantanations and permutations.

Incidentally, every single language I know of does use implicit records:
For domains of procedures. As a mathematician, I find the asymmetry between
the treatments of domain and codomain bizarre.

And a parting, jocular shot: [Mark again, refering to another posting which
I lost. I am very likely quoting out of context. Apologies, but I couldn't
resist.]
    Reading the code at the point of the call there is no syntactic
    evidence about which parameters are which.  When huge semantic
    differences are hidden by identical syntax, that is a bad thing.
I agree:-^. VAR parameters are used for (1) out parameters, (2) in-out
parameters, and (3) passing pointers to save space. Let us outlaw VAR
parameters, and make programmers say which of the three they are really
doing.


--
Vidhyanth Rao			It is the man, not the method, that solves
function.mps.ohio-state.edu	the problem. - Henri Poincare
    (614)-366-9341		[as paraphrased by E. T. Bell]

dwiggins@atsun.a-t.com (Don Dwiggins) (08/31/90)

In article <1990Aug28.203643.11214@zaphod.mps.ohio-state.edu> vidynath@function.mps.ohio-state.edu (Vidhyanath K. Rao) writes:

   Incidentally, every single language I know of does use implicit records:
   For domains of procedures. As a mathematician, I find the asymmetry between
   the treatments of domain and codomain bizarre.

You'd probably like logic programming then.  Not only can there easily be
multiple out parameters, a parameter can be out in one call and in in
another.  I don't know what Dijkstra thinks of Prolog, but this symmetry
occurs naturally and is often a useful conceptual and practical tool.


--
Don Dwiggins				"If you think training is expensive,
Ashton-Tate, Inc.			 try ignorance"
dwiggins@ashtate.a-t.com		 -- Derek Bok, Harvard U.

dinucci@ogicse.ogi.edu (David C. DiNucci) (08/31/90)

In article <DWIGGINS.90Aug30195906@atsun.a-t.com> dwiggins@atsun.a-t.com (Don Dwiggins) writes:
>In article <1990Aug28.203643.11214@zaphod.mps.ohio-state.edu> vidynath@function.mps.ohio-state.edu (Vidhyanath K. Rao) writes:
>
>   Incidentally, every single language I know of does use implicit records:
>   For domains of procedures. As a mathematician, I find the asymmetry between
>   the treatments of domain and codomain bizarre.
>
>You'd probably like logic programming then.  Not only can there easily be
>multiple out parameters, a parameter can be out in one call and in in
>another.  I don't know what Dijkstra thinks of Prolog, but this symmetry
>occurs naturally and is often a useful conceptual and practical tool.

Prolog likely suffers from exactly the kind of complexity in
data flow that the original attribution to Dijkstra seems to have
warned about.  Strand (a parallel processing variant) at least
partially addresses this by identifying parameters as either in or out
when a procedure (clause, process) is defined.

Large-Grain Data Flow 2 (aka F-Nets) takes the approach that the
single out restriction is fine for much of the low-level implementation
of any large system, and that traditional imperative textual sequential
languages are well suited to express such computation.  But at
higher "system" levels, modules which each do a fair amount of work,
and thus are likely to return many different kinds of results, are
being composed.  Plugging these results into a record structure just
so they can be pulled back apart again is nonproductive, but allowing
multiple out parameters in a textual syntax complicates the tracing of
data (and in LGDF2's case, control) flow in just the way Dijkstra
claims.  The problem here is not *multiple out parameters*, but
*textual syntax*.  A graphical syntax clears those problems up rather
nicely, and as stated, is only proposed for the high-level module
composition.  A module is a function where each argument is identified
as in, out, inout, or neither (control flow only), and the graphical
syntax shows the data flow clearly with arrows on one, both, or neither
end of each arc representing an argument.  The symmetry between domain
and codomain is preserved.

This approach was borrowed directly (by Babb) from DeMarco-style
Structured Analysis data flow diagrams, as an attempt to forego the
intermediate step of translating the diagram to a structure chart before
implementation.  The current aim of the model, other than the
software-engineering aims expressed above, is to provide an
architecture-independent model for parallel processing.

-Dave

-- 
David C. DiNucci                             Oregon Graduate Institute
                                             (Formerly Oregon Graduate Center)
CSNET: dinucci@cse.ogi.edu                   19600 N.W. Von Neumann Dr.
UUCP:  ..ucbvax!tektronix!ogicse!dinucci     Beaverton, Oregon  97006

fs@rex.cs.tulane.edu (Frank Silbermann) (08/31/90)

In article <11831@ogicse.ogi.edu>
dinucci@ogicse.ogi.edu (David C. DiNucci) writes:
>
>	... at higher "system" levels
>	modules are being composed which each do a fair amount of work,
>	and thus are likely to return many different kinds of results.
>	Plugging these results into a record structure
>	just so they can be pulled back apart again is nonproductive.

When a collection of diverse of values
forms a single conceptual unit,
it should be treated as a unit within the program.
Therefore we define a record-type to represent the concept,

If a module's different kinds of results
do _not_ form a conceptual unit,
then the design is kludgy and should be redone.
What ever happened to the idea that every module
should perform a single conceptual operation
(i.e. produce a single conceptual result)?

	Frank Silbermann	fs@rex.cs.tulane.edu
	Tulane University	New Orleans, Louisianna  USA

cik@l.cc.purdue.edu (Herman Rubin) (09/01/90)

In article <4019@rex.cs.tulane.edu>, fs@rex.cs.tulane.edu (Frank Silbermann) writes:
> 
> In article <11831@ogicse.ogi.edu>
> dinucci@ogicse.ogi.edu (David C. DiNucci) writes:
| >
| >	... at higher "system" levels
| >	modules are being composed which each do a fair amount of work,
| >	and thus are likely to return many different kinds of results.
| >	Plugging these results into a record structure
| >	just so they can be pulled back apart again is nonproductive.
> 
> When a collection of diverse of values
> forms a single conceptual unit,
> it should be treated as a unit within the program.
> Therefore we define a record-type to represent the concept,
> 
> If a module's different kinds of results
> do _not_ form a conceptual unit,
> then the design is kludgy and should be redone.
> What ever happened to the idea that every module
> should perform a single conceptual operation
> (i.e. produce a single conceptual result)?

Kludgy it is not.  For example, a procedure can produce a result and a
domain indicator, such as the truth of a condition.  Or an integer
quotient and a floating remainder can be produced.  Or a short integer
exponent and a mantissa.  Or the decomposition of a word into fields,
one being translated into a sign, another into an integer, and the 
rest into a floating point number. 

All of these are natural and useful multiple outputs.  Putting these 
into a record to extract later is very definitely non-productive, just
as encoding multiplication as repeated addition is non-productive.
-- 
Herman Rubin, Dept. of Statistics, Purdue Univ., West Lafayette IN47907
Phone: (317)494-6054
hrubin@l.cc.purdue.edu (Internet, bitnet)	{purdue,pur-ee}!l.cc!cik(UUCP)

fs@rex.cs.tulane.edu (Frank Silbermann) (09/04/90)

David C. DiNucci:		dinucci@ogicse.ogi.edu <11831@ogicse.ogi.edu>
>| >	... at higher "system" levels
>| >	modules are being composed which each do a fair amount of work,
>| >	and thus are likely to return many different kinds of results.
>| >	Plugging these results into a record structure
>| >	just so they can be pulled back apart again is nonproductive.

Frank Silbermann:	fs@rex.cs.tulane.edu <4019@rex.cs.tulane.edu>,
>> Every module should perform a single conceptual operation
>> i.e. produce a single conceptual result,
>> and should be treated as a unit within the program
>> (i.e. as a record).  If a module's different kinds of results
>> do _not_ form a conceptual unit, then the design is a kludge.
 
In article <2501@l.cc.purdue.edu> cik@l.cc.purdue.edu (Herman Rubin) writes:
>	All of these are natural and useful multiple outputs:
>	...a result and a domain indicator, e.g. the truth of a condition.
>	...an integer quotient and a floating remainder.
>	...a short integer exponent and a mantissa.
>	...the decomposition of a word into fields -- one a sign,
>	   another an integer, and the rest a floating point.

(And each combination can be viewed as a single object.  So...?)

>	Putting these into a record to extract later
>	is very definitely non-productive,
>	like encoding multiplication as repeated addition.

The time loss due to encoding multiplication
as repeated addition is O(n).  Grouping atomic information
into records hurts efficiency by at most a constant factor
(and an optimizing compiler might eliminate even that).

A better analogy would the claim that
"storing a register into memory, only to later load it back,
is definitely non-productive, and therefore its is better
to program in assembler language."  (Such claims have in fact been made.)

The real question is where a program ought to lie
on the machine-oriented/human-oriented continuum.
This is an economic($$) decision, with no universal answer.
For most applications, however, the economics
are shifting to favor more human-oriented programming,
and this includes the clumping of detail into fewer,
but larger and more meaningful, concepts.
That's what records are for.

Frank Silbermann	fs@rex.cs.tulane.edu
Tulane University	New Orleans, Louisianna  USA

cik@l.cc.purdue.edu (Herman Rubin) (09/05/90)

In article <4060@rex.cs.tulane.edu>, fs@rex.cs.tulane.edu (Frank Silbermann) writes:
 
> David C. DiNucci:		dinucci@ogicse.ogi.edu <11831@ogicse.ogi.edu>
| >| >	... at higher "system" levels
| >| >	modules are being composed which each do a fair amount of work,
| >| >	and thus are likely to return many different kinds of results.
| >| >	Plugging these results into a record structure
| >| >	just so they can be pulled back apart again is nonproductive.
 
> Frank Silbermann:	fs@rex.cs.tulane.edu <4019@rex.cs.tulane.edu>,
> >> Every module should perform a single conceptual operation
> >> i.e. produce a single conceptual result,
> >> and should be treated as a unit within the program
> >> (i.e. as a record).  If a module's different kinds of results
> >> do _not_ form a conceptual unit, then the design is a kludge.
  
> In article <2501@l.cc.purdue.edu> cik@l.cc.purdue.edu (Herman Rubin) writes:
> >	All of these are natural and useful multiple outputs:
> >	...a result and a domain indicator, e.g. the truth of a condition.
> >	...an integer quotient and a floating remainder.
> >	...a short integer exponent and a mantissa.
> >	...the decomposition of a word into fields -- one a sign,
> >	   another an integer, and the rest a floating point.
 
> (And each combination can be viewed as a single object.  So...?)
> 
> >	Putting these into a record to extract later
> >	is very definitely non-productive,
> >	like encoding multiplication as repeated addition.
 
> The time loss due to encoding multiplication
> as repeated addition is O(n).  Grouping atomic information
> into records hurts efficiency by at most a constant factor
> (and an optimizing compiler might eliminate even that).

Assuming fixed length numbers being added, this is so.  But if
one has 64-bit numbers being multiplied, and performs an addition
every nanosecond (I do not believe that this has been quite yet
obtained), it can take almost 600 years to perform the multiplication.

The storage and reloading process right now can impose a factor of
5 or more now.  If load/store continues to get slower compared to
arithmetic, the O(n) term gets larger, so maybe it is no longer O(n).

> A better analogy would the claim that
> "storing a register into memory, only to later load it back,
> is definitely non-productive, and therefore its is better
> to program in assembler language."  (Such claims have in fact been made.)

Given a weakly-typed, overloaded-operator, user-extensible method of
representing machine operations, with a good flexible macro processor,
I would very definitely prefer to program that way.  At least at the
present time, and for a long time to come, the compiler cannot even
begin to recognize what the thinking human finds elementary.

> The real question is where a program ought to lie
> on the machine-oriented/human-oriented continuum.
> This is an economic($$) decision, with no universal answer.
> For most applications, however, the economics
> are shifting to favor more human-oriented programming,
> and this includes the clumping of detail into fewer,
> but larger and more meaningful, concepts.
> That's what records are for.

So the human, who understands what is needed in terms of machine operations,
is to be restricted to those clumsy manipulations which the language
designer managed to comprehend.  This attempt to limit humans to doing
what the idiot is capable of, and leaving the rest to the machine, is
frustrating to the thinking human, as well as being noneconomic.  It 
results in library programs operating at low speed.  The idea that those
writing programs to be included in a software library should be restricted
to what an undergraduate is expected to do in a beginning programming
course is what the above quoted paragraph states.

Mathematicians have been dealing with these situations for centuries.  That
all calculations which can be done on a computer can be done clumsily on a
Turing machine does not mean that that should be the way to go.
-- 
Herman Rubin, Dept. of Statistics, Purdue Univ., West Lafayette IN47907
Phone: (317)494-6054
hrubin@l.cc.purdue.edu (Internet, bitnet)	{purdue,pur-ee}!l.cc!cik(UUCP)

tom@stl.stc.co.uk (Tom Thomson) (09/05/90)

In article <4060@rex.cs.tulane.edu> fs@rex.cs.tulane.edu (Frank Silbermann) writes:
>The real question is where a program ought to lie
>on the machine-oriented/human-oriented continuum.
>This is an economic($$) decision, with no universal answer.
>For most applications, however, the economics
>are shifting to favor more human-oriented programming,
>and this includes the clumping of detail into fewer,
>but larger and more meaningful, concepts.
>That's what records are for.
>
I think records are a red herring here.

We may well want to introduce a record type into the language, just so that
we can 
       (i) have a label for the type of a function with multiple results
      (ii) pretend those multiple results are really all one thing 
 
There is no need to introduce any objects of these types, though.
 
If I write something like
 
declare decompose:real -> bool * int * int;
  ........
let (sign, expt, mant) = f(r) .....
 
then I have provided the call to f with three output parameters (named sign,
expt, mant) as required by Rubin while using the structuring concept suggested
by FS.
 
But I hope my system never constructs a record at run time.
 
Tom

dlawson@grebyn.com (Drew Lawson) (09/06/90)

In article <2501@l.cc.purdue.edu> cik@l.cc.purdue.edu (Herman Rubin) writes:
>> 
>> If a module's different kinds of results
>> do _not_ form a conceptual unit,
>> then the design is kludgy and should be redone.

>Kludgy it is not.  For example, a procedure can produce a result and a
>domain indicator, such as the truth of a condition.  Or an integer
>quotient and a floating remainder can be produced.


All of these examples show an output of a single conceptual object.
Returning a quotient and remainder is simply returning the result
of a division.  This does not complicate things.

The problem is procedures which return (set VAR parameters, etc.)
a quotient, remainder and the size of the largest available memory
block.  This is three data items representing two unrelated values.


-- 
+--------------------------------------------------------------------+
| Is life an illusion?                          | Drew Lawson        |
| Or does it just seem that way?                | dlawson@grebyn.com |
+--------------------------------------------------------------------+

jgk@osc.COM (Joe Keane) (09/11/90)

I think returning a record just gets around the issue.  We don't use a single
record to pass all the in parameters to a function, so why should we do it for
out parameters?

The original `problem' with multiple problems was really with pointer
aliasing, or more generally, modifier aliasing.  If objects are returned,
there is no possibility of conflict.  The caller may actually allocate space
on the stack for the returned objects, but this is an implementation issue and
there is still no possibility of conflict.

There are two ways to deal with aliased modifiers.  One way is to say that the
modifications are actually sequential.  Therefore, if two modifications are
done to the same object, one of the modifications is lost, or at least doesn't
determine the most current version.  The order may be pre-defined, so say the
textually earlier parameter is the one that is lost.  Or the order may be
undefined, so that which ever modifier gets there first loses.

The other way is to insist that the modifications are done together, which is
more like what we'd expect in a functional language.  Then each expression
which writes a return value expects to be the last one before the function
exits.  This works fine as long as they don't both want to write to the same
place.  What happens if they are aliases?  In that case each one has to go
before the other.  Therefore the problem is deadlock.  This makes sense if you
think about it; the two problems are really closely related.  Two modifiers
want exclusive rights to determine a single value, the current state of the
returned object.  They can't both have it.

norman@d.cs.okstate.edu (Norman Graham) (09/12/90)

From article <3788@osc.COM>, by jgk@osc.COM (Joe Keane):
> I think returning a record just gets around the issue.  We don't use a single
> record to pass all the in parameters to a function, so why should we do it for
> out parameters?

Counter Example. In my favourite language, Miranda, every function 
takes exactly one value and returns exactly one value. The question
of whether either of those values are tuples (i.e. values of a product
type--think of cartesian products of sets here) is irrelevant.

This sort of behaviour corresponds well with the set-theoretic view of
a function--a map from one set to another.
-- 
Norman Graham                            Oklahoma State University
  Internet:  norman@a.cs.okstate.edu     Computing and Information Sciences
  BangPath:                              219 Mathematical Sciences Building
     {cbosgd,rutgers}!okstate!norman     Stillwater, OK  USA  74078-0599

news@usc.edu (09/12/90)

In article <3788@osc.COM> jgk@osc.COM (Joe Keane) writes:

   I think returning a record just gets around the issue.  We don't
   use a single record to pass all the in parameters to a function, so
   why should we do it for out parameters?

What's wrong with getting around the issue?

I can see the need for having two "in parameters" (otherwise, joining
hitherto distinct pieces of information is akward), but I fail to see
the need for multiple "out parameters" in a functional language.
Excuse me for being dense, but what is the range?

If you need to do something on the order of passing different pieces
of information (about some core datum) to multiple function
applications, the proper way would seem to be to construct some
derived function which applies the base functions properly.  (e.g.
J's conjunctions)

eerke@cs.kun.nl (Eerke Boiten) (09/12/90)

In article <3788@osc.COM> jgk@osc.COM (Joe Keane) writes:
>I think returning a record just gets around the issue.  We don't use a single
>record to pass all the in parameters to a function, so why should we do it for
>out parameters?

In transformational programming (and, probably, elsewhere) it is often
convenient to assume that functions have just one argument (which may be
a tuple). So we *do* use a single record to pass all the in parameters
sometimes.

The problem of using a record for the out parameters is that the
function f below is not the identity function:

f(x) = 7*a+a where (a,a) = x/7

(assuming x/7 = (x DIV 7, x MOD 7))
but who would disagree to this being a (static) semantic error?

Eerke Boiten
Department of Informatics (STOP Project), K.U.Nijmegen
Toernooiveld, 6525 AD Nijmegen, The Netherlands
Tel. +31-80-612236.	Email: eerke@cs.kun.nl