Re: Seeking computer-programming job (Sunnyvale, CA)

T

Thomas A. Russ

Lew said:
An experienced Java programmer will see this series of 'instanceof'
operators and look for a way to code the thing polymorphically.

interface Multi<T>
{
public String getValue( T arg );
}
class StringVersion implements Multi<String>
{
public String getValue( String arg )
{
return getStringValue( arg );
}
}
class SprayVersion implements Multi<Spray>
{
public String getValue( Spray arg )
{
return getSprayValue( arg );
}
}

etc.

So, then you either need to carefully wrap everything you want to handle
in a new object just in order to do the dispatch on it for this one
method.

(BTW, this is caused by the inability to add methods to a Java class
once it is defined. Another nice, flexible benefit of CLOS).
 
K

Kaz Kylheku

["Followup-To:" header set to comp.lang.lisp.]
Assume for a moment I had advanced macro capabilities in Java.
I'd write:

public static String getMultiValue( Object valueObject )
{ String result = null;
if( valueObject == null )result = null;

else if( StringValue stringValue =? valueObject )
result = getStringValue( stringValue );

else if( SprayValue sprayValue =? valueObject )
result = getSetValue( setValue );

return result; }

The macro here is marked by the occurence of »=?«, which is

Please don't use non-USASCII characters in comp. discussion groups.
The = character will do.
not a regular Java operator, but part of my hypothetical macro
call pattern.

The problem here is readability:

Is it?
Can others know, what = means in my macro package?
Can they know how to immediately find its documentation?

You should be prepared to raise exactly the same objection for any other
identifier you define: class, function, variable, etc.

Do others know what getMultiValue means in your library?
Can they immediately find its documentation?

Obviously not if there is no documentation, and not
immediately if it's not indexed and cross-referenced.

What does that have to do with anything?
If I invent and use this macro now, will even I myself
remember its meaning a year later?

If you invent a function now, will you remember its meaning?
The same coded in Java without macros /is/ more verbose,
but also more readable to someone who knows Java:

By this logic, the code behind a function is also more readable.
public static String getMultiValue( Object valueObject )

Similarly, I have no clue what this call means:

getMultiValue(valueObject)

Of coruse the name suggests that it gets a multi value, whatever
that is. But macros also have suggestive names just like that.

So now if you replace all occurences of that function
by the following code, it's more obvious:
{ String result = null;
if( valueObject == null )result = null;

else if( valueObject instanceof StringValue )
{ StringValue stringValue =( StringValue )valueObject;
result = getStringValue( stringValue ); }

else if( valueObject instanceof SprayValue )
{ SprayValue setValue =( SprayValue )valueObject;
result = getSetValue( setValue ); }

return result; }

So like, let's not use functions; they obfuscate code.

We should also recursively expand getSetValue too in the above.

Basically, code is only readable when it uses only the built-in syntax, and
only standard library functions that come from the language specification.

Thus, everything should be written as one large brace statement with no
confusing named abstractions that invoke something elsewhere.

A novel reads nicely from cover to cover in one pass, and a program should be
similarly readable from opening brace to closing brace.
 
K

Kaz Kylheku

Also, macros can be abused. I have done that when I was learning
Lisp, and only learned how to write better macros gradually with the
guidance of the kind people on c.l.l. I can imagine that in a
corporate environment with many programmers, one would be wary of
inexperienced programmers writing macros, especially if you don't have
the time or inclination to teach them better because of high personnel
turnover rates.

I learned Lisp macros in about a week, and have always written great ones.

:)

Mileage varies.
 
S

Spiros Bousbouras

On 15 May, 14:47, (e-mail address removed)-berlin.de (Stefan Ram) wrote:

This isn't so much a direct response as a tangential comment
hence I'm not quoting any text.

I remember reading somewhere on the internet that the reason
Java does not have macros is because the creators of Java were
worried that it would lead to the creation of many mutually
incompatible and incomprehensible dialects. I remember that what
I read contained the phrase "we may even have heard of Lisp" or
something similar but a Google search did not find it. I did
find the following blog post though which compares syntactic
abstractions through macros with OO abstractions. Quite
interesting.

http://debasishg.blogspot.com/2007/01/syntax-extensibility-ruby.html

The argument that macros might lead to incomprehensible
subdialects of a language has been put forward on
comp.lang.python on some occasions when macros were discussed. I
haven't kept any links but if you search for "macros" on
comp.lang.python you'll find such threads. On at least 2
occasions a story was presented where someone was working in
some university department , a few research teams were working
on the same problem , each team had written their own macro
extensions to address this problem and because of that each
group could not understand the code of the other groups. Lisp
people who participated in such discussions pointed out that
this can happen with functions just as well as it can happen
with macros.

I'm on the Lisp side of this. As long as one doesn't
intentionally write obfuscated code and documents one's macros I
don't believe that macros present any greater dangers of
incomprehensibility than any other programming construct.
 
K

Kaz Kylheku

Not in Java. In Java, you can use:

/**
* Removes non-<code>Runnable</code> objects from <code>list</code>
* and returns pruned list as a <code>List&lt;Runnable&gt;</code>;
* caller is responsible for synchronizing the list if necessary.
*/
@SuppressWarnings("unchecked")
/*
* Justification: the only unchecked cast is in the return
* line, and all non-Runnables have been removed from the
* list at that time. The returned list is a list of
* Runnables unless a data race occurred. Correct behavior,
* as ever, depends on the caller synchronizing where
* appropriate.
*/
public List<Runnable> pruneList (List<?> list) {
for (Iterator<?> i = list.iterator(); i.hasNext();) {
// Old "for" used intentionally
Object o = i.next();
if (!(o instanceof Runnable)) i.remove();
}
return (List<Runnable>)list;
}

Untested.

The above vomit maens:

;; By default, all things are assumed non-runnable.

(defmethod is-runnable (obj) nil)

;; But things subclassed from runnable, well, are.

(defmethod is-runnable ((obj runnable-class)) t)

;; Function call to remove non-runnables from a some-list, returning
;; a new list with only runnables in it:

(remove-if-not 'is-runnable some-list)

REMOVE-IF-NOT is a functional construct which leaves the old list alone
(but the new list may share substructure with the old). If you
want to modify the original list, use DELETE-IF-NOT.

(setf some-list (delete-if-not 'is-runnable some-list))

Obviously correct by inspection.
 
S

Series Expansion

There's a easier and more powerful way to solve that kind of problem.
It is to write a DSL (ie. macros) in Lisp, to generate correct code in
the first place.

Of course, you will object how to be sure the macros are correct?
I'll object how to be sure your type expressions are correct?

The compiler checks them. The type and object system ends up being a
DSL, but one whose syntax and type-semantics can be checked by
automation. Your DSL will just crash with some run-time error if you
goofed.

The major benefit is with later, incremental changes rather than
during system design though. Making the incremental change to the
static-type system is like plugging something in where, if you get the
plug backwards, it won't fit. Making the incremental change to the
dynamic-type system is like plugging something in where, if you get
the plug backwards, it seems fine until you flip the on switch, and
then you have inconvenienced the local fire brigade.

There's a tradeoff, to be sure. Dynamic seems to beat static for small
jobs and certain kinds of tinkering, and for things that are used
interactively by a hacker that can fix or reprogram them on the fly.
For stand-alone deployments of large systems to non-developer users,
on the other hand, static seems to win out.

Static does mean spending some time figuring out and then coding the
type system. However, the time spent *figuring out* the type system
needs to be spent no matter what, or the durn thing won't work. The
time spent *coding* the type system does two things:

1. It documents the types of things in the code. If the code will
ever have to be maintained by someone unfamiliar with it,
including perhaps your own future self after a hiatus, this will
need to be done anyway or the code won't be maintainable.
2. It lets the compiler catch type errors quickly, and locate them
quickly. With dynamic typing, a type error will produce a
run-time error that may appear far from the actual location of
the bug, for instance of the wrong sort of object is added to
a collection and much later removed, then an operation invoked
on it that it doesn't support. I'm not sure what you'd get in
lisp, probably a logic error rather than an exception; Smalltalk
would produce the dreaded "doesNotUnderstand" box and Java a
ClassCastException or NoSuchMethodError (Object taken from
raw List and either cast or operated on reflectively).
Java with a List<Foo> is another story: as soon as you write a
line of code that tries to put a non-Foo into the list, it
gets a red wavy underline in your IDE!

This gets into the flip-side of the tradeoff. Besides developing the
type system and documenting the types of values and variables in the
code, which probably needed to be done in some manner anyway, you've
traded off a bit more coding time for a lot less debugging time. Some
errors you will discover within *seconds* of making them that would
have required *hours* to hunt down otherwise. That's savings you can
take to the bank. Literally, if you're a manager paying a bunch of
coders' salaries, and their productivity has just shot up through the
roof.

The system size is probably key, and whether the end-user can be
expected to interactively debug and tinker with it. For small systems,
the code base is not large enough for hunting down the type error to
take hours, and dynamic languages seem to better support on-the-fly
modification for various reasons. Glue scripts are another case in
point.

Furthermore, it's worth noting that most "statically typed" languages
give you the option to take or leave compiler validation of your type
logic, while most dynamically typed languages are exactly that -- no
such option, you can't have it even if you want it.

C lets you dodge the type system completely with arbitrary casts and
the "union" construct. So does C++, though C++ adds a dynamic_cast
operator that acts like Java's cast (i.e. is a type-checking cast).
Java, as noted, lets you use nothing but Object and reflection, or
lots of casting and untyped collections, if you want to (most Java
programmers don't, though -- I wonder why?). All of them let you
eschew object orientation and just have structs/classes with public
fields and external functions to manipulate these if you want to, for
that matter. Heck, you can defeat the type system in Java almost
entirely by using arrays of Objects, eschewing named fields and the
primitive types like int and double.

Nobody does this though.
And unless your type system is Turing Complete, there are necessarily
specifications you cannot write.

The type system doesn't need to be Turing-complete unless it needs to
be genuinely dynamic, changing at run-time, or else essentially
embodying the entire semantics of the code.

Type systems normally embody the *static* aspects of the code only --
what sorts of things go here, what this thing can do rather than what
specific thing is here, what this thing is currently doing.

When a complex relationship exists, like "this should be an integer
unless that's null, and then it should be a Rocket instead" are better
expressed with an encapsulated object, with an integer field, a Rocket
field, and some other field, and the other field determining which of
the first two are in use. Callers to the object needn't concern
themselves with this internal matter, and the preservation of the
required invariants is the responsibility of the small amount of code
in one single class. If the invariant is ever violated, you can
immediately narrow down the location of at least one bug to that small
bit of the codebase.

If a similar invariant is ever violated in a dynamic system that
accesses those fields from all over and has complex, Turing-complete
type-system rules of some sort, you'll have to hunt for it all over
the place, by contrast. Any code that touches those fields is suspect.
Indeed, since the object with these fields could have snuck into
someplace it didn't belong, without a peep from the compiler, any code
at all is suspect, since code meant to store an integer in a hash
field in something else might have accidentally overwritten your
Rocket after being passed the wrong type of object. And of course the
code that represents the Turing-complete type-system rule might
contain the error. Maybe the fields didn't get monkeyed with at all,
and the run-time error claiming they did was itself issued in error.
And when your type system is Turing Complete, there are necessarily
bugs written in it, and a lot of time spend on it, because of the
syntactic complexity (eg. C++ templates!).

Upfront cost, downstream savings (if done right).
Lisp macros are just a much simplier and much more powerful to archive
the same results much faster.

What results? Compile-time type safety? I doubt it, unless you're
writing a C or Java compiler in Lisp. :)

Maybe you mean to suggest that the type-checking be done dynamically,
at run-time, with type-checking code everywhere when things are added
to/retrieved from collections, passed as parameters, and everything.
Java does this to some extent, when there's a need to cast something
or when something's taken out of a generic collection. However, in
many circumstances like parameter-passing the type-checking has no
runtime overhead because the compiler was able to verify everything
before the program ran.

This is another area where dynamic tends to lose to static:
performance. In the absence of dynamic type checking, the system is
probably somewhat buggy and unstable, which costs in downtime. In the
presence of dynamic type checking, the system may be more robust,
because bugs were easier to find and fix during testing, but areas not
covered by testing will be dodgy and the dynamic type checking takes
its toll on speed. In the presence of static type checking, 100% of
the code is free of type errors according to the compiler, before it's
even run, so areas not covered by testing still won't contain type
errors (though they may contain logic errors). The smaller amount of
dynamic type checking takes much less of a toll on performance. Last,
but certainly not least, because your compiler actually groks the type
system it may be able to make all kinds of clever optimizations that
would not be safe to make on dynamically-typed code. The speed boost
from this alone could be the deciding factor for CPU-bound tasks, or
for systems with strict real-time requirements (which, in practice,
are almost always coded in C).
 
J

John B. Matthews

(e-mail address removed) wrote:

[snip]
The test program (chk_all.sd7) starts other programs one by one. It
does this with popen(). The system function popen() opens a pipe and
the result of the command execution can be read from this pipe. To
read (hopefully) the whole pipe into a string the function 'gets'
is called with the request to read 999999999 characters. The Seed7
function 'gets' is implemented with the C function filGets()
(in file_rtl.c). The function filGets() is really low level (other
interpreters/librarys probably also have similar functions) and
uses several strategies to do its job. In order to work reasonable
good in the common case (reading just some characters) memory for
the requied maximum length is requested with malloc(). After the
data is read the result string is shrinked to the actual size (with
realloc). When the memory cannot be requested a different strategy
is used. In this case the function trys to find out the number of
available characters (which is possible for a regular file but not
for a pipe). If this fails also a third strategy is used. A smaller
block is requested, this block is filled with data and resized in a
loop. So it is nothing special when a malloc fails in the filGets()
function. There are alternative code paths to provide the requested
functionality.

Limiting the length to 0x1FFFFFFF (~0.5 GiB) in gets() eliminates the
warning:

$ include "seed7_05.s7i";
include "shell.s7i";

const proc: main is func
local
var file: outFile is STD_NULL;
begin
outFile := popen("./hi -q echo.sd7 echo test", "r");
writeln(gets(outFile, 16#1FFFFFFF));
end func;

There are a number of warnings when compiling fil_rtl.c, but I don't see
a connection:

fil_rtl.c: In function ‘getBigFileLengthUsingSeek’:
fil_rtl.c:263: warning: passing argument 1 of ‘bigFromUInt64’ as
unsigned due to prototype
fil_rtl.c:265: warning: passing argument 1 of ‘bigFromUInt32’ with
different width due to prototype
fil_rtl.c: In function ‘filBigLng’:
fil_rtl.c:315: warning: passing argument 1 of ‘bigFromUInt64’ as
unsigned due to prototype
fil_rtl.c:317: warning: passing argument 1 of ‘bigFromUInt32’ with
different width due to prototype
fil_rtl.c: In function ‘filBigTell’:
fil_rtl.c:389: warning: passing argument 1 of ‘bigFromUInt64’ as
unsigned due to prototype
fil_rtl.c:391: warning: passing argument 1 of ‘bigFromUInt32’ with
different width due to prototype
fil_rtl.c: In function ‘filSetbuf’:
fil_rtl.c:848: warning: passing argument 4 of ‘setvbuf’ as unsigned due
to prototype
The point is that the Mac OS malloc() complains when it is not
capable to do its job. This is not according to the specification
of malloc(). According to all definitions of malloc it should just
return NULL when it cannot allocate the requested amount of memory.
Did I mention that other operating systems did not have the
slightest problems when I tell them to malloc huge amounts of
memory. Either they deliver it or they return NULL. No performance
problem or error message.

I think it does return NULL; the helpful message appears on stderr.
One solution would be: Find out a way to tell Mac OS malloc()
that it should behave according to the specification (stop
complaining). Maybe there is a compiler flag, a #define a callback
function or something else. When this is not possible a work
around is necessary.

Turning off the alarm still leaves one curious about the smoke:)
 
S

Series Expansion

Ok, then let me summarize lisp macros as being hooks in the lisp compiler..

Imagine what you could do if you could modify your compiler on the fly:

- you could blow things up in completely new and exotic ways

- you could generate bugs it would take a crack team of comp.sci
Ph.D.s to track down

- you could find previous/other programs no longer compiling, or
worse, compiling but no longer working correctly once recompiled

The potential here is for foot-bullets express-delivered by a fully-
automatic assault rifle instead of the revolver Java gives you, C's
shotgun, or C++'s, semiauto pistol.

It's not a surprise that it's heavily used in comp.sci research but
you don't see many desktop applications written in Lisp, then.

It has its niche. But I see problems with its ability to achieve
widespread use for more ordinary and common-place applications.
 
S

Series Expansion

Not only can you modify your compiler on the fly,
you can generate code and modify your program on the fly.

There are certain languages where you *can* do that,
but I doubt that they make that sort of functionality *easy*.

Forget what I said earlier about fully-automatic assault rifles. This
lets you shoot yourself in the foot with a tactical nuclear bunker-
buster.

Your code base has one bug. Of course the compiler can't see it
(dynamic typing). So you run it. Whoops. Now your code base has fifty
bugs, because the one bug was in code that modified it!

Happy debugging. See you in 2040 or thereabouts. :)
 
S

Series Expansion

It does not rely on any internal representation
of the functions by some implementation - this would make the code
unportable - but it provides a macro that retrieves the function's
body to be stored into a file and then restored back again. It is as
simple as this: instead of writing (lambda (...) ...) you write (st
(lambda (...) ...).

Cool. But why store a function that's there in your code anyway?

Also, the commonplace lisp problem of short names. The name "st" in
particular isn't particularly clear in meaning, and moreover, you'll
quickly run out of short names to use in a big enough project anyway.
In Java I am pretty sure this isn't conceivable because

1) Java does not have dynamically created functions (or am I wrong?)
2) Java Code is not an easy manipulable data
3) Java doesn't have macros.

As you can see, macros not only provide a way to make your
applications shorter or more readable, but they also make it possible
to do somethings that otherwise couldn't be done.

Java is Turing-complete, so it can probably be done.

You can certainly implement an interpreter in Java that can save and
load its code. This also lets the function be created at run-time from
other data or user-input.

You can also monkey about with ClassLoader and byte arrays and
bytecode, or (since Java 6) with ClassLoader, Compiler, and Strings
full of Java source code, and make classes on the fly, save them to
disk (e.g. as .class files), load them, instantiate them (with
reflection), and invoke them (with reflection, or polymorphically
through a compile-time-known base class or interface).

So yeah, Java can do it, though it's clunky. And Java can do it with
"functions" created on the fly, not just some existing one like
Math.sin(double). In the case of using ClassLoader and eventually
calling the object via a base class or interface rather than
reflection, and using a JIT-enabled JVM, it can even run the new
function with performance rivaling compiled and optimized C code.
 
S

Spiros Bousbouras

Cool. But why store a function that's there in your code anyway?

Also, the commonplace lisp problem of short names. The name "st" in
particular isn't particularly clear in meaning, and moreover, you'll
quickly run out of short names to use in a big enough project anyway.

There is no "commonplace lisp problem of short names". If you want
to write long descriptive names in Lisp you can do it just fine.
 
G

gugamilare

- you could blow things up in completely new and exotic ways

- you could generate bugs it would take a crack team of comp.sci
  Ph.D.s to track down

- you could find previous/other programs no longer compiling, or
  worse, compiling but no longer working correctly once recompiled

The potential here is for foot-bullets express-delivered by a fully-
automatic assault rifle instead of the revolver Java gives you, C's
shotgun, or C++'s, semiauto pistol.

It's not a surprise that it's heavily used in comp.sci research but
you don't see many desktop applications written in Lisp, then.

It has its niche. But I see problems with its ability to achieve
widespread use for more ordinary and common-place applications.

It does not work like that at all, it is very different from that
actually. Macros don't CHANGE your code, they GENERATE code. They
can't CREATE bugs on your code. They themselves can be buggy, exactly
like any other function, but they won't kill your code like that. And
they don't introduce bugs in the compiler either - it is just plain
simple list manipulation. You could write macros using only if, cons,
car and cdr, and maybe some arithmetic functions to compute some
number. Of course this is not the best way to do it, but, behind the
scenes, it is what is being done.

It would be comparable to some function in Java that generates text
that is going to be evaluated by the compiler as the real code instead
of the macro call. Someone said that Seed7 templates are just a
function that is run during compile time. Well, that is the very
definition of Lisp macros.

There are systems written in CL that survive (or survived) for years
running WHILE being developed / debugged / used, so you CANNOT say
that CL have this huge security hole, or that it is that simple to
blow everything up.

If you don't want to learn Lisp to really know what macros are, fine,
but don't start flaming about things that you don't understand. I did
not say that Java is a bad language.
 
G

George Neuner

On 13 Mai, 10:52, Nicolas Neuss <[email protected]>
wrote: [...]
Hmm, I consider myself a Lisp fan and would not want to choose between
these two statements.  Maybe you should refine your simplistic world
view a little qbit?
As I already said: This is not my simplisic world view.
Proof: Just tell me about great language features that are
missing in Lisp.

This is a really strange request, because language features aren't
arbitrarily composable. In a lot of circumstances adding one feature
means you've got to get rid of, or compromise, another feature. C-
style pointers strike me as the best example of this kind of feature;
they're a great feature for C to have, but you don't want them in a
lot of other languages because the make it so easy for you to light
your own hair on fire[1]. Common Lisp relegates them to the realm of
non-standard extensions, which is (IMO) as it should be.

The problem with C's pointers is they went too far in allowing
arithmetic on them. There isn't any need for that at the source
level. Anything you can do with a C pointer you can also do more
safely with a C++ reference and indexing.
[Not that C++'s references can't also be abused - they could be made
safer. I'm just limiting the examples to the C family.]

There are any number of other features like that. Ease of
implementation is another feature that can be tremendously valuable,
and is one that Common Lisp doesn't pay a whole lot of attention to.

Cheers, Pillsy

[1] Or, in the case of Fortran, because it makes some important
optimizations impossible.

Source level pointer arithmetic makes those same optimizations
impossible in C.

George
 
P

Pascal J. Bourguignon

Series Expansion said:
- you could blow things up in completely new and exotic ways

- you could generate bugs it would take a crack team of comp.sci
Ph.D.s to track down

- you could find previous/other programs no longer compiling, or
worse, compiling but no longer working correctly once recompiled

Strangely enough, this doesn't occurs in Lisp.
Are lisp programmers super geniuses?
Or are they just not imaginative enough to shoot themselves in the foot?
By the way, why would any body want to shoot himself in the foot?
No, that's silly, we just don't do that.

Yes, you could blow things up, but if you've got more than three
neurons, you won't.

Yes, you could create bugs, but it's easier to avoid them.

Yes, you could render your language incompatible. But lisp
programmers don't have to implement a whole new language to be able to
incorporate their favorite language feature, so they don't (contrarily
to the Perl, Java, Python, Ruby, etc, language creators), and
consequently, you can still run programs that are 50 years old on
modern lisp system.

The potential here is for foot-bullets express-delivered by a fully-
automatic assault rifle instead of the revolver Java gives you, C's
shotgun, or C++'s, semiauto pistol.

Well, perhaps Lisp programmers are the type of guys to ask what the
red button is used for before pressing it... In anycase, we don't
observe holes in lisp programmers' feet.

It's not a surprise that it's heavily used in comp.sci research but
you don't see many desktop applications written in Lisp, then.

It has its niche. But I see problems with its ability to achieve
widespread use for more ordinary and common-place applications.

That's strange. Are you sure you are looking at lisp? When I do, I
see solutions, not problems.
 
G

gugamilare

Cool. But why store a function that's there in your code anyway?

Also, the commonplace lisp problem of short names. The name "st" in
particular isn't particularly clear in meaning, and moreover, you'll
quickly run out of short names to use in a big enough project anyway.




Java is Turing-complete, so it can probably be done.

Oh, yes, of course, because there is even a CL compiler written in
Java :p

That is not exactly what I meant. You can do anything, but how non-
extensible, non-compatible with existing code and complicated this
would be? The library that I made is pretty small, straightforward and
readable, and it fits inside CL perfectly, with small changes when you
want to use it.
You can certainly implement an interpreter in Java that can save and
load its code. This also lets the function be created at run-time from
other data or user-input.

An interpreter would be slow. My macro does not change the function
that will be generated, so it is as fast as it would be without my
macro. But, okay, you do that and in about two months you show me your
thousands of lines long code that you had to do to accomplish that.
You can also monkey about with ClassLoader and byte arrays and
bytecode, or (since Java 6) with ClassLoader, Compiler, and Strings
full of Java source code, and make classes on the fly, save them to
disk (e.g. as .class files), load them, instantiate them (with
reflection), and invoke them (with reflection, or polymorphically
through a compile-time-known base class or interface).

Why don't you write your own entire compiler instead with the new
feature added? This what you want to do is just painful, just to add a
small feature to the language. If anyone needed to do this in Java, he
or she would not do it, and change the approach he or she is using to
not need it. That is what I meant by "it can't be done".
 
K

Kaz Kylheku

["Followup-To:" header set to comp.lang.lisp.]
An experienced Java programmer will see this series of 'instanceof'
operators and look for a way to code the thing polymorphically.

interface Multi<T>
{
public String getValue( T arg );
}

This is cheating; you are redefining the object's type to suit the code.

Now valueObject must be a Multi<something> .

Can you make this gadget Just Work on the type Object?

Common Lisp:

(defmethod get-value ((obj string-value))
(get-string-value obj))

(defmethod get-value ((obj spray-value))
(get-spray-value obj))

Done; now we can juse use get-value to pull out the value of ither a
string-value or a spray-value.
class StringVersion implements Multi<String>
{
public String getValue( String arg )
{
return getStringValue( arg );
}
}

Even worse, you are wrapping objects now, yuck.

God for job security in tough economic times, though.
 
S

Seamus MacRae

Kaz said:
The above vomit

Fascinating. An illogical, emotional outburst in response to ordinary
computer code.
maens:

;; By default, all things are assumed non-runnable.

(defmethod is-runnable (obj) nil)

;; But things subclassed from runnable, well, are.

(defmethod is-runnable ((obj runnable-class)) t)

;; Function call to remove non-runnables from a some-list, returning
;; a new list with only runnables in it:

(remove-if-not 'is-runnable some-list)

REMOVE-IF-NOT is a functional construct which leaves the old list alone
(but the new list may share substructure with the old). If you
want to modify the original list, use DELETE-IF-NOT.

(setf some-list (delete-if-not 'is-runnable some-list))

Obviously correct by inspection.

Whoops. There are at least two things missing from your version:

1. Type-safety. There's nothing to identify the output list as
containing only runnables. Therefore there's nothing to identify
the input list to someplace else as containing only runnables. So
the compiler won't catch it if the wrong list gets used there.
2. Documentation. About half my code was documentation. The first
comment would be processed by the Java tool-chain to produce
an HTML file documenting the method, its behavior, and the
conditions under which it was guaranteed to do the right thing.
Your code has no such documentation, and indeed you didn't
even define a callable function prune-list or whatever, let
alone make it so that anyone could right click on the word
"prune-list" in some code subsequently and get a concise little
pop-up telling them exactly what it did and what its preconditions
were. Certainly I haven't a clue how thread-safe your
implementation is just from looking at the code.

Your remove-if-not version sounds like it might be thread-safe, because
it sounds like it behaves like my second (and not-quoted-by-you) code
sample, which copies the list. Someone else expressed concern about the
time spent copying objects (though in Java, at least, you'd be copying
pointers rather than whole objects).

Your delete-if-not version sounds like it probably is not thread-safe.
Is there a way to associate a monitor with the list and lock it in lisp?
There is in Java, and it's very simple:

synchronized (list) {
runnableList = pruneList(list);
for (Runnable r : runnableList) {
r.run();
}
}

This removes the non-runnables and runs the runnables in sequence, while
holding a monitor associated with the list.

As an aside, your version seems to be prescriptive rather than
descriptive -- def statements that execute rather than declarations of
classes and methods. If so, this points to a different problem: what
happens if there are circular dependencies? In Java, if two classes or
other chunks of code need to refer to one another they can. In Lisp, it
sounds like an imperative interpreter runs down a list of def-foo
commands to actually build the program structure. If it executes the
statement that creates the foo-bar function first, and this references
the baz-quux function, the latter will still be undefined.

This can also happen in C but C lets you declare a function prototype
without specifying its implementation, or name a struct or similarly, so
you can subsequently reference it; you would ordinarily prototype foobar
and bazquux together in a header and then provide their implementations
in a .c file, which would include that header; the foobar implementation
can then refer to the bazquux one without any problems. C++ is similar,
though you can also implement them as inlines in the header:

int bazquux(string);

inline bool foobar(string s) { return bazquux(s) == 0; }

inline int bazquux(string s) {
int r = get_length(s);
if (r == 0) return 0;
return r + foobar(get_prefix(s))?1:0;
}

(with get_length and get_prefix presumed defined somewhere else, and
get_prefix(s) always shorter than s).

Java is even smarter: you can just have methods, classes, and suchlike
refer to each other, click build in your IDE, and off you go.

Java also acknowledges that the "shift" key has other uses than just
with the 9 and 0 keys. :)
 
G

gugamilare

Cool. But why store a function that's there in your code anyway?
Oh, I forgot this question. Because in Lisp you can create functions
like objects. The canonical example:

(defun make-adder (n)
(lambda (x) (+ x n)))

The function make-adder receives a number n, and creates a function
that adds whatever number x it receives to n.

Of course, this is a small example of what function can do.
Dynamically created functions is a feature that all functional
languages have.
 
S

Series Expansion

(BTW, this is caused by the inability to add methods to a Java class
once it is defined.  Another nice, flexible benefit of CLOS).

Another potential source of headaches too, I'll warrant. Imagine the
chaos that would ensue if Java did allow this. Someone might add a
method to the String class called "foo" to do X. Someone else adds a
method to the String class called "foo" to do Y. Then someone tries to
use both pieces of code together in the same project and kaboom!

The only way out looks like adding a whole 'nother system of package
namespaces *inside* *each and every class* for its methods.

Even then you get dependency headaches. Code calling someString.foo()
won't compile if you don't have the right non-standard String
extension in your classpath somewhere. It's one thing if you have
instanceOfSomeUnfamiliarClass.foo() and obviously need a library
installed, but when code calls non-existent methods on standard
library classes like String, who you gonna call? A google search for
"SomeUnfamiliarClass" might turn up the needed library; a google
search for "String" isn't likely to help much.
 
S

Series Expansion

 (defmacro with-open-file ((var filename &rest open-options) &body body)
   `(let ((,var nil))
      (unwind-protect
        (progn
          (setq ,var (open ,filename ,@open-options))
          ,@body)
        (unless (null ,var)
           (close ,var)))))


  (with-open-file (out filename :direction :eek:utput)
     ;; do-stuff
    )

The statically-typed language C++ lets you do something equivalent,
thanks to RAII. You create a local variable out that's an output
stream, open it, do stuff with it, and then let the local variable go
out of scope. However that happens (normal or exception) the output
stream's destructor does the job. So there's no need for even two
lines of boilerplate there.

At this point it looks like C++ > Lisp > Java on this score.
ProcessFile.processOpenFile(filename,
                            new FileProcessor () {
                              public void processFile(OutputStream out) {
                                // do stuff
                              }
                             });

where we create a new anonymous class instance to encapsulate the body
of the code that we want to execute.

So, it is possible to build up such abstractions in Java, but to my eyes
that looks a lot clunkier and less integrated than the Lisp macro
solution.  You see, there isn't any way of introducing new forms into
Java that look like the language itself.  So there isn't any way to add
your own control structure construct that looks like

 with_open_file (out, filename) {
    // do stuff
  }

which would be the real equivalent of the Common Lisp macro solution.

Not yet. There are plans afoot to address this, in Java 7 or (more
likely) Java 8, by adding some sort of syntactic sugar here, perhaps
full-blown closures.

Then it might be C++, Java > Lisp.
 

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