Thomas said:
Precisely. It doesn't enter into this at all, in most cases.
It is nicely in the background for when you need it, but it doesn't
intrude into your day-to-day programming most of the time.
Except the hypothetical situation at issue was when you wanted to add a
new dispatch for a method and the "defgeneric" dispatch-table thingie
was not in the same namespace you want your own code to be in.
Which means that Anonymous C Lisper dodged the issue instead of
addressing it.
Um, yes.
(defgeneric frobnobdicate (a b)

method ((foo foo) (bar bar)) ;;arguments
.. body)

method ((foo foo) (baz baz))
..body))
has to become something else, I'm guessing
(defgeneric frobnobdicate (a b)

method ((foo foo) (bar bar)) ;;arguments
.. body)

method ((foo foo) (baz baz))
.. body)

method ((foo foo) (quux quux))
..body))
or something close to that, to add quux to the set of classes it
dispatches on.
They just add their own method by just doing
(defmethod frobnicate ((foo quux) (baz quux))
..body)
and this method gets added. They do this in their own source code.
They don't need to do anything to your source code.
That is in contradiction with how Anonymous C Lisper said it was done.
Furthermore, the generic function frobnicate exists in *some* namespace.
Whose? If it's not yours, you'll either be intruding, or you'll be
creating a parallel generic function with the same name in a different
namespace. In the latter case, the two generic functions won't be
compatible anymore than java.util.Date and javax.sql.Date are. You
couldn't have x.frobnicate() (or rather, Lisp's equivalent syntax)
dispatch to foo's frobnicate if x was a foo and to quux's if x was a
quux; if you had imported Anonymous C Lisper's frobnicate x.frobnicate()
would not know about quux (only foo, bar, and baz) and if you had
imported your frobnicate x.frobnicate() would not know about foo (or bar
or baz), only quux. In either case, it would do what you wanted for one
type of value in x and give some sort of runtime error for the other.
It's the same problem, except swapping nouns and verbs, as trying in
Java to have a method whose return type was simply "Date" and then
sometimes return a java.util.Date and sometimes a javax.sql.Date from
it. (The best you can do is return a common supertype, in this case
likely having to be Object.)
Perhaps you were confused because ...
I will not entertain public speculation regarding my mental functioning.
Please refrain from such. I will not respond to that kind of nonsense
except to trim most of it when replying and make a note similar to this
one that it is not polite behavior.
That is ONE way to do it, but in Lisp there are often multiple ways to
accomplish the same thing.
And therefore a high likelihood of confusion being created, particularly
as to where something is getting its value or definition from.
What I've seen so far of Lisp in your posts looks like an excellent
recipe for creating tightly-coupled code that will end up being a
nightmare to debug and maintain in any seriously large real-world
project. Localizing generic functions in one spot in the code causes
code-ownership-respecting change-request bottlenecks or else people
stepping on one another's toes. Disseminating them throughout the code
base, on the other hand, causes the code that's affecting *your* code to
be widely dispersed and hard to locate.
Haven't you guys ever heard of encapsulation?
Having a lot of flexibility in a language takes some getting used to
This is also one of the touted "strengths" of perl. Everyone knows what
the result was: a messy hodgepodge with so many ways to do anything that
code reads like line noise and understanding other peoples' code
requires an encyclopedic knowledge of ALL of the ten zillion ways to do
everything.
Not a problem for small, single-developer projects.
HUGE problem for huge, multi-developer projects.
Hence the recent large scale exodus from perl to ruby and (to a lesser
extent) Python. The recent explosive growth in ruby (and especially
rails) has to a significant degree been fueled by perl users jumping ship.
but the philosophy of Lisp is to
provide a wide range of tools and techniques and rely upon programmers
being smart enough to pick the tool that is most appropriate to the
problem at hand.
Fine for single-developer projects.
In multi-developer projects, you have to also rely upon programmers
knowing ALL of the tools, every single last one of them.
Java only barely dodged this bullet, due to the literate-programming
influences on it. Java code contains all kinds of library calls into the
vast, almost incomprehensibly complex standard library, and often into
third-party libraries too. Fortunately, decent Java IDEs let you get
from some code using an unfamiliar class or method to the documentation
for that method almost instantly.
This doesn't work for perl (or, I expect, Lisp) because a) perl at least
lacks this bundle-the-docs-with-the-code-in-a-tool-recognized-format
aspect of Java, and b) even were it otherwise, stuff built into the
language itself lacks this sort of "JIT documentation" anyway. In Java
it's not a problem; although your IDE won't let you point at "int" and
get a primer on the built-in "int" type or point at "class" and get an
explanation of that, there's only a small amount to understand, and it
easily fits within the head of a single programmer. Not so with perl
and, apparently, Lisp; the former has, and the latter apparently has,
TONS of built-in operators and other things that are not going to have
"JIT docs" and that ARE all going to appear in code written by a large
enough team of developers, you can betcher aspidistras they will.
Well, that is one of the nice things about CLOS
Much the reverse, as I have just explained. CLOS is an inside-out object
system; instead of providing powerful tools to create encapsulation to
insulate some code from changes to other code, it seems to provide
powerful tools to destroy encapsulation and change the behavior of
arbitrary other code. In a single-developer or serial-developer
environment, that might be powerful, but in a team-development
environment, it's a train wreck waiting to happen.
There have been lots of large systems built with lisp, and these
things have been given a fair amount of thought.
The problem is that developing a large system without teamwork, with
only one coder at a time, takes eons.
It's no wonder Lisp hackers enjoy good job security at the few job
positions they hold; they're going to be working on whatever they're
building until retirement, if not until doomsday.
Just because the solution is different from Java's solution doesn't
make it ineffective.
No, of course not. It's the solution not scaling to large devteams that
makes it ineffective.
See again my hammer and picnic blanket analogy for why you might want to
be able to do new things with existing objects.
I think that analogy was already torpedoed in another post.
Suppose you wanted to graph a tree of objects. java.lang.Class
doesn't have a graph() method, and you can't add one.
Fortunately for those of your coworkers who rely on its behavior.
A Java programmer create an external iterator that traverses classes
somehow.
In CLOS you just define a graph method specializing as necessary
So a Lisp programmer has the option: create an external iterator that
traverses classes somehow, or muck with a system class and thereby break
everyone else's code in the project, thus getting the entire rest of the
team mad at them and probably winding up fired.
Oh, yeah, except that Lisp programmers work alone, or at best in pairs.
They more or less have to.
I doubt the scientific validity of this, but we'll let that pass.
It's quite valid. It's not a whole-web search, it's a search of just the
definitions at dictionary.com, where nearly all occurrences of "noun"
are in the form of word (noun) definition, and nearly all occurrences of
"verb" are in the form of word (verb) definition. I'd say the figures
are within 5% of the actual numbers of nouns and verbs on the site. (It
would have been nice, though, if they'd had a page with such statistics
that I could find.)
It still doesn't follow that one would choose a methodology that makes
one or the other of these approaches MORE difficult, when one can
instead have a methodology where adding a noun or adding a verb are
equally easy.
But you can't have such a methodology. There are only three possibilities:
One does package.Noun.verb;
One does package.verb(Noun);
One does package1.verb(package2.Noun) or package1.Noun.package2.verb.
The third is clearly cumbersome and difficult. Eliminate it. The first
makes it easy to add new nouns, and new verbs to new nouns, only. The
second makes it easy to add new verbs, and new nouns to existing verbs,
only. So the first makes it hard to add new verbs to existing nouns and
the second makes it hard to add new nouns to existing verbs, while the
third makes it easy to do both but hard to USE the darn things after.
The choice between the first two is then made by which one does more
often, add new nouns or add new verbs.
Apparently one adds new nouns about 6x as often as one adds new verbs.
The statistics on overall language size suggest it, and my own personal
experience bears it out.
So, you advocate then arbitrarily making about 1/6th of your work harder
than it needs to be because, well it's only 16% of the time?
No. I advocate choosing to make 16% harder (package.Noun.verb) over
making 84% harder (package.verb(Noun)) or 100% harder
(package1.verb(package2.Noun) or package1.Noun.package2.verb).
Um. You are making unwarranted assumptions again.
No, I am not, and you will kindly refrain from using accusatory
1) You are expected to use pre-existing generic functions. That's how
you extend the existing library code base.
The problem is that doing so leads to problems in large-team shops when
conflicting modifications get made to basic code used systemwide. This
is why most programming languages don't let you make any modifications
to basic code used systemwide whatsoever. Lisp permitting such
modifications will cause team-size scaling problems.
2) You are NOT expected to even have access to the source code. You
don't need it. You shouldn't mess with it.
So, the spirit of closed-source no-serviceable-parts-inside is still
alive and well in at least one place besides Redmond, WA.
All you need to know is what the API for the generic function is.
That lets you call it but not add to it. To add to the generic function
in Anonymous C Lisper's post (the thing with the "(foo foo) (bar bar)"
stuff in it) obviously requires its source code.
That is all you need know is the signature and the semantics (the
latter so you know that this is actually the generic function you want).
Works for generic functions. Doesn't work for macros, since little
implementation details like the names of internally used variables in
the macros and how many times it references one of its parameters will
change its behavior. Without the source code, trying to debug problems
caused by extra side effects or by collisions between your variables and
variables inside the macro will be a nightmare.
Someone else in this thread has gone into a lot more detail about this,
posting a detailed and fairly convincing argument that some kinds of
macros could not be implemented without the macro having at least one of
three undesirable traits: non-reentrant and either not thread-safe or a
concurrency bottleneck; may cause local variable name collisions; and
may cause multiple occurrence of argument side effects.
Of these, the former is a pretty big deal, and the latter two mean
subtle bugs could arise where the macro got used that would be hard to
figure out without access to its source code.
3) The name already exists in the namespace, so you aren't changing the
namespace at all.
That's sophistry. You could use the same statement to argue that
altering the behavior of java.lang.String was not "changing" the
java.lang namespace at all.
(BTW: Namespaces in Common Lisp are exactly
that. They are NAMEspaces and only concern themselves with the
resolution of names. They don't get overloaded with notions of
functions, classes, or anything extraneous like that)
Java packages don't, either, save that they get their own documentation
blurb and there's a "within this namespace only" level of access control
between protected ("same-namespace and subclasses") and private ("this
class and nested classes only", roughly).
Of course, the latter probably mystifies you, like everything else to do
with encapsulation.
4) Assuming that you are extending the generic function in a reasonable
manner, for example by adding methods that dispatch on types that
you have defined, then there is no "lone-wolf hacker" part to this
at all. It is a controlled extension of the library. It is no more
"lone-wolf hacker" than subclassing a library class in Java would be.
Subclassing a library class doesn't change the base class's behavior.
Whereas letting anyone edit
(foo foo) (bar bar)
(foo foo) (baz baz)
to
(foo foo) (bar bar)
(foo foo) (baz baz)
(foo foo) (quux quux)
necessarily also allows editing it to
(foo foo) (mumble mumble)
(foo foo) (baz baz)
and people frobnobdicating bars elsewhere in the project getting a nasty
surprise next time integration tests are run.
This does not make sense.
That doesn't stop you from either creating a subclass of that library
class or extending the generic function by adding methods.
Except that the latter is done by modifying the piece of code with the
defgeneric thingy, at least for a nonempty subset of generic functions
if not for all of them.
Perhaps that is the analogy that you need to understand this. In Common
Lisp adding a method to a generic function is the same as creating a new
subclass of an existing class. It extends the generic function by
allowing it to apply to a new combination of method argument types.
It's not the concept that concerns me. It's the actual mechanics.
Altering the dispatch table to add something means having write access,
which means having the ability to also alter or remove something,
perhaps unintentionally while trying to add something. It's a fruitful
source of breakage, it encourages tight coupling, and in some projects,
it might even be considered a security risk.
It is, in essence, adding a dynamically dispatched "subclass" to the
generic function.
Assuming no slips of the finger while editing the defgeneric thing, in
cases where that's how the particular generic function is defined
instead of that other way.
Methods are not in packages.
This contradicts what you guys said earlier. Earlier you intimated that
verbs (and maybe nouns AS WELL) were in namespaces.
If methods really are not in packages, there goes the neighborhood. Name
collisions are going to be causing an unbelievable amount of headaches
in any sufficiently large project.
A Common Lisp package is simply a namespace.
So is a Java package, for the most part.
It controls the mapping from surface strings to symbols.
Ditto, if by that you mean "resolving non-fully-qualified names in the
source code at compile time".
(Computer science really ought to adopt a more uniform terminology for
such common concepts.)
the package system affects only the mapping of strings to names when
lisp code (or data) is read.
Seems to confirm the above.
Only symbols are in packages.
I don't see that this is a useful distinction, between "the method is in
the package" and "the method's name is in the package". It amounts to
the same thing. Using Java-like syntax, as I'm more familiar with it:
import package3.verb;
package1.verb
package2.verb
verb
will mean package1's version, package2's version, and package3's
version, in order. Whether you say "verb" is in the package or the
particular method is in the package, this isn't changed. In fact, it
doesn't seem to me to make much sense to say that "verb" itself is in a
particular package.
If by "symbol" you mean the equivalent of the fully-qualified name, then
yes, "package1.verb" is in package1 and so forth, but the important
thing to the programmer is that the different "verb"s avoid colliding,
and the choice of package to import affects how occurrences of
unqualified "verb" are resolved.
Then the distinction is entirely useless; whether the verb itself, as in
the code that does something, or simply the (fully-qualified) name used
to access it, is in package3 seems to be a meaningless quibble.
Especially given Lisp's lack of Java's default-access and protected
modifiers, which might be argued to make the distinction more meaningful
(but with nouns replacing verbs).
There is a difference between using and making changes. Even if you
can't (or shouldn't) make changes to a namespace, you can still use the
existing names that are in there.
I would consider changing the code associated with those names to be
making changes in the namespace.
I would consider altering java.lang.String to add a new verb quux to it
to enable aString.quux() to be making an alteration in java.lang.
Likewise I would consider altering a Lisp verb lisp-verb in
some-namespace to add a new noun quux to it to enable (lisp-verb
some-quux) to be making an alteration in some-namespace.
Just as you can subclass any class you like (except Java final
classes), you can extend any generic function in Common Lisp.
That doesn't change the namespace.
I'm sorry, but it does. Contrast:
package foo;
public class FooException extends Exception { }
which extends Exception (in java.lang) with FooException (in foo) and a
hypothetical addition of a Lisp class quux to a foo package with a
dispatch added for quuxes to a generic function in a bar package. You
aren't just subclassing something in the bar package, you're actually
changing the behavior of something in the bar package.
In the Java version you don't have to touch anything in java.lang. The
JVM figures out on the fly during class-loading what dispatch tables to
use. Maybe if the generic function in the bar package was defined the
"defmethod" way the same holds for the Lisp compiler or VM. However, if
the generic function was defined the "defgeneric" way, the way Anonymous
C Lisper did his, then there's a problem, because there's an explicit
dispatch table in the code instead of the compiler making one. To
frobnobdicate a quux, code belonging to the bar package would have to be
edited, and not just code belonging to the foo package (where quux
itself is).
It merely extends the generic function, much as sublasses extend
their parents.
In the above example, the addition of FooException does not prevent
people using the original Exception.
Extending a generic function doesn't seem to me to create two versions,
keeping the original version under the original name and adding a new
version under a different name (ala Exception and FooException).
Instead, it *changes* the generic function (frobnobdicate and
frobnobdicate). This means nobody can use the original frobnobdicate --
and every bit of code referring to it refers to the new frobnobdicate now.
Obviously, havoc would ensue if subclassing Exception caused every bit
of code using Exception to suddenly be using FooException instead.
In fact, though you repeatedly analogize extending a generic function to
subclassing, it seems to be more like editing Exception itself and
adding a new method to it. Maybe it doesn't disturb the existing code
that uses Exception (except for breaking subclasses that added a
same-named method that couldn't be either an overload or an override of
the new base-class method), but it does change something in java.lang,
and it also breaks encapsulation, big-time.
Your generic functions sound to me somewhat like data-less classes, more
like a Java interface with one method specified than like a function.
Only where Java interfaces are implemented by adding "implements Foo" to
other classes, only some Lisp generic functions are analogously
"implemented"; others, the ones defined using "defgeneric" as in
Anonymous C Lisper's post, are more like a hypothetical alternative kind
of interface that lists a method name and a list of the implementing
classes and is implemented by adding an entry to that list. It therefore
cannot be implemented without changing code used by other parts of the
project, potentially breaking things.