Newsgroups: alt.lang.design,comp.lang.c++,comp.lang.lisp
Path: cantaloupe.srv.cs.cmu.edu!das-news2.harvard.edu!news2.near.net!howland.reston.ans.net!spool.mu.edu!bloom-beacon.mit.edu!news.kei.com!newshost.marcam.com!charnel.ecst.csuchico.edu!waldorf.csc.calpoly.edu!kestrel.edu!mcdonald
From: mcdonald@kestrel.edu (Jim McDonald)
Subject: Re: Comparing productivity: LisP against C++ (was Re: Reference Counting)
Message-ID: <1995Jan4.034958.2309@kestrel.edu>
Sender: mcdonald@saker.kestrel.edu (Jim McDonald)
Nntp-Posting-Host: saker.kestrel.edu
Organization: Kestrel Institute, Palo Alto, CA
References: <19941203T221402Z.enag@naggum.no> <BUFF.94Dec15103904@pravda.world> <D0xAIp.3Dn@rheged.dircon.co.uk> <vrotneyD11MDv.Ks7@netcom.com> <vogtD12y8D.HLL@netcom.com> <3d5alh$6j7@celebrian.otago.ac.nz> <3d8j9r$cm9@celebrian.otago.ac.nz> <1994Dec23.020527.25551@kestrel.edu> <3dg0d2$m7f@celebrian.otago.ac.nz>
Date: Wed, 4 Jan 1995 03:49:58 GMT
Lines: 219
Xref: glinda.oz.cs.cmu.edu comp.lang.c++:105836 comp.lang.lisp:16262

(My apologies again for the size of this exchange, but at least there are
 no transcripts this time.)

In article <3dg0d2$m7f@celebrian.otago.ac.nz>, nmein@bifrost.otago.ac.nz (Nick Mein) writes:
|> Jim McDonald (mcdonald@kestrel.edu) wrote:
|> 
|> : Common Lisp implementations tend to provide a lot of such error handling
|> : automatically--in fact much of that handling is specified by the ANSI
|> : standard, although offhand I'm not sure where the exact boundaries are.
|> 
|> I still feel that there is an important difference between default error
|> handling (no matter how desirable this may be during exploratory
|> programming) and application-defined error handling.

I agree.  I was just pointing out that the kind of error handling you did
is included as part of the default handling in most (all?) Common Lisp
implementations.  A C++ module presumably could be written to handle all 
the same kinds of defaults in as graceful a manner, and then it could
get standardized, and then C++ would become as productive as Lisp for this
kind of example.  But C++ isn't there yet.

|> : |> Also (maybe significantly) there does not appear to be the equivalent
|> : |> of my "assert(data)" - an important check that some client of the class
|> : |> will not get away with doing something stupid - like manipulating an image
|> : |> that hasn't been loaded yet!
|> 
|> : In lisp, runtime type-checking catches many such errors, entering an
|> : interactive debugger from which repairs can be attempted.
|> 
|> : Using the quick hack I wrote:
|> 
|> :   > (read-image ".cshrc")
|> 
|> You appear to have missed the distinction between stuff-ups by the
|> programmer, and run-time errors. As I suggested in an earlier post, try
|> writing:
|> 
|>     (setf im (make-instance 'image))
|>     (invert im)
|> 
|> Does this error get caught?

I think someone else addressed this.  Since you didn't say what the default
initial values are, I used some arbitrary legal values.  So they would get
inverted to some other arbitrary but legal values.   

|> : |> Oh, I see. Checking the number of command line arguments, checking
|> : |> the return value of system calls, returning a meaningful value to
|> : |> the system is a lot of junk.
|> 
|> : Well, it is nicer to get all of that automatically without having
|> : to clutter your code with it:
|> 
|> :   > (read-image "/tmp/hack" 47)
|> :   >>Error: READ-IMAGE called with 2 arguments, but only 1 argument is allowed
|> 
|> Again, you seem to be confusing stuff-ups by the programmer (calling a
|> function with the wrong number of parameters) with run-time errors
|> (running the program with the wrong number of arguments, or invalid
|> arguments; io errors, etc).

But running a program with the wrong number of arguments is just an
instance of calling a function with the wrong number of parameters.
In the lisp example, the top level invocation looks like any other
function call, whereas in the C example, two different syntaxes are
used.  

[Btw: I followed your use of "parameter" although I would normally
 have used "argument" in that context.  I think of parameters as
 part of the function, bound to arguments supplied by the caller.]

|> 
|> : Well, 6 minutes (or whatever it took Erik) + .27 second + .56 seconds
|> : certainly seems like a win over 60 minutes + 0.0 seconds.
|> 
|> Are you being serious? 

Yes.  I assumed you wanted a quick hack to invert a few images.
Apparently you wanted something more, but that was only clear
from your comments below.

On a larger note, I've seen people (well, shoot, might as well admit
it, I've done it myself) spend hours of their time to optimize code
that will ultimately save a total of seconds or minutes of CPU time.
A horrible trade-off unless you're doing it for the learning experience.
For all I knew, you were making that mistake.

|> |> I was hoping that people would (if they took the challenge seriously at all)
|> |> look at the spirit of the challenge without me laying down the rules 
|> |> explicitly. Marco Antoniotti did so, and so did you with your second & third
|> |> versions. Thanks.
|> 
|> : I tried, but to be honest, I really didn't know what you wanted--it wasn't
|> : an idle complaint.
|> 
|> Fair enough - I've re-read my original post, and it wasn't too specific.
|> The challenge was to build a simple "image" class, implementing three
|> operations/methods (load, invert, save). The class was to be engineered
|> to a degree that it would be suitable as a component in a larger system.

Hmmm.  I then would argue that neither of our examples succeed very
well, since both are far too ad hoc, and we're given no good clues 
as to what that larger system might do.   (I'm really not trying 
to be difficult--it's just that bottom-up guesswork for good reusable
components is a hard problem.  It effectively requires the choice of 
a paradigm, and sometimes there are many different paradigms with very 
different tradeoffs.)

|> : It was a step towards the third version where the data has a variable
|> : format, given by the header.   Again, I ask you how long it would take
|> : to revise your program to accept an array of arbitrary byte formats,
|> : e.g. 128-bit bytes, with any number of dimensions, e.g. 256 * 256 * 4 * 2,
|> : where the formats are given in the data file.  Such a capability seems
|> : to be far more object-oriented than the minimal encapsulation you used.
|> : I think your little *p hack becomes problematic.
|> 
|> If you like, email the file-format specs to me & I'll have a go and let
|> you know (next year some time).

Use whatever format you like.  I just used a pair of forms to be read 
before the real data was input:

 (unsigned-byte 16)
 (2 3 4)
 <real data>

but you could do something like an initial line of the form NN D1 D2 ... Dn,
where NN is the bits per byte and D1 through Dn are the magnitudes along
each dimension.  You can even avoid parsing and use a sequence of 
binary words terminated by zero.  Whatever works.   Bytes should be
allowed to get fairly long -- at least a few hundred bits, and 
perferably limited only by available memory.

Or you could ignore the problem--it's pretty feeble as a challenge.

A better problem would be to provide something like the following 
in a few hours:

  (1)  Allow the data to describe a multi-plane image where each
       plane has its own numeric type for the array values (1-bit 
       bytes, 1024 bit bytes, double precision floating point, 
       complex ratios, quaternions, whatever...).
       The planes might represent multiple colors, or a basic image
       and intensity gradients, or a basic image and traced edges, 
       or tomographic data, or ...

  (2)  Allow the user to provide an ordered tree of operations, 
       which will be applied in the order the leaves appear.  
       (I.e., each atomic operation is a filter, and a stack of 
       filters can be used as a new filter.) 

       An operation might change the number and/or character of 
       the planes in an image.

  (3)  Provide a few examples to invert a grayscale image, compute
       the gradient of a grayscale (e.g. using Roberts Cross),
       covert RGB (3 planes) into a stippled grayscale pattern, etc.

I think that's much closer to the kind of day-to-day code I write
in lisp (except that low-level graphics isn't a domain I normally 
look at very much).

|> : If I really cared fantastically about speed I'd spend a few hours
|> : to add a compiler-macro to produce hand-coded optimal assembly
|> : code for the target machine whenever the compiler saw the pattern 
|> : to be optimized.  That tends to create programs a few per cent 
|> : faster than C compilers can create.
|> 
|> Well, this is getting very close to verifying my feeling that claiming
|> that "programming in Lisp is 2x to 10x more productive than programming
|> in C++ (regardless of the problem domain)" is simply not true.

Two points:

 (1) In most programs, 80 or 90 percent of the time is spent in 10 or
     20 percent of the code.  Sometimes 99 percent is spent in .1
     percent of the code. 

     Using your example, an invert image operation seems unlikely to
     ever account for more than a fraction of a percent of a total 
     application's runtime, so speeding it up or slowing it down even 
     by a factor of 5 or 10 probably won't be detectable to the user
     of that application. 

     Since the runtime for almost any piece of code is down in 
     the noise when compared to the total runtime of the application
     it runs in, the burden on you as a programmer is to first prove
     that some code *needs* to run fast before you waste your valuable
     time to make infrequently used code run fast,   

 (2) It is fairly rare to care *fantastically* about performance outside
     of a few low level things like lexers, graphics primitives, etc.

     A case I had in mind was for a product that did neural net
     simulations and most of the time was spent in generalized dot
     product computations as part of the back-propagation algorithm.

     I diassembled a few examples of the various dot product routines
     (each had a different set of parametric assumptions), modified 
     the assembly code by hand to remove some extraneous instructions, 
     and then used that assembly code as a template to craft a compiler
     macro that would examine its arguments at compile time and produce 
     the optimal register usage for a given choice of parameters.

     The resulting commercial product was thus 20% faster than it's C 
     competitors, even though it had more features than them, because
     each place that computed such a dot product had inline hand-optimized
     code.   That was a half-day well spent.   Meanwhile, I did virtually
     nothing to optimize the vast bulk of the code, which was time 
     well saved. 


     





          
