Re: macros
On May 20, 10:01 pm, Seamus MacRae <smacrae...@live.ca.nospam> wrote:
Alessio Stalla wrote:
On May 20, 7:07 pm, Seamus MacRae <smacrae...@live.ca.nospam> wrote:
Alessio Stalla wrote:
What if the package system scoped generic "names", and those "names"
could be used to refer to verbs, nouns, pronouns, etc. etc.?
That would be even worse: not only would methods and classes both have
to be scoped, so thing.method() would have to have two scopes decided
somehow (and the type of "thing" cannot be a factor in compile-time na=
me
resolution, since as far as the compiler is concerned it could be
anything), but method names and class names could now collide with one
another, and possibly with other things too!
They don't
Sure they do. Either two identical names collide, or they reside in
distinct namespaces. You just insinuated that methods and classes do not
reside in distinct namespaces (quoted material, very top of this post).
Therefore if one of each with the same name resides in a single one,
they collide. It's elementary logic, Alessio.
Do a field named foo and a method named foo in the same class collide
in Java? No.
Are they in the same namespace? Yes, because a class creates a
namespace in Java.
What happens is that, while foo is always the same symbol
(com.package.Class.foo), the compiler knows where it names a variable
and where it names a method, and those are two distinct cases, so no
collision ever appears. Lisp works just the same, only the namespace
is not created by a class but by an ad-hoc object, a package. So for
example you can have the symbol foo in package p, which happens to
name a variable and a function, and symbol foo in package q, which is
another symbol, and names a variable and a class.
At first this might not seem so bad, since nouns and verbs are mostly
nonoverlapping sets. But some common cases are exceptions: a jump vs. =
to
jump, for example.
There has been some example code and other text that collectively
implied that Lisp identifiers are not case-sensitive, which means that
using a Java-esque convention of Jump for the noun and jump for the ve=
rb
would not avoid a collision.
Worse, this also means JUMP for a global constant will collide with bo=
th.
What a mess!
the method MY:frobnodicate(MY:quux) is different from
the method MY:frobnodicate(YOUR:quux) which is different from
the method YOUR:frobnodicate(MY:quux) which is different from
the method YOUR:frobnodicate(YOUR:quux)
The methods, yes. It's the little bit of code containing the dispatch
table that concerns me.
Someone has to add a (YOUR:quux YOUR:quux) dispatch to MY:frobnobdicat=
e, for example.
Only if he/she wants to use MY:frobnobdicate with YOUR:quux, which may
or may not make sense depending on what MY:frobnobdicate and YOUR:quux
mean.
In your example, though, this combination was used, along with all three
other combinations of whose frobnobdicate and whose quux.
My example was meant to show all the possible combinations, not
suggest they usually are all used.
it probably does not make much sense to do
(defmethod db-library:open-transaction ((transaction-manager picture-
library:jpeg-picture)) ...whatever...)
I don't see why not. Storing jpegs as BLOBs in a DB is not unheard-of.
See the parameter name - it's transaction-manager. A jpeg picture is
not a transaction manager, so this fictional method does not make much
sense.
If that someone is you, then
you may become a bottleneck in the development effort. If that someone
is the owner of YOUR, then everyone can trample your MY:frobnobdicate
garden. Either way, as team size increases there will be growing pains=
..
Add to that the misery of the user of these methods having to sort the=
m
all out and explicitly scope at least part of at least three of them a=
nd
all of at least one of them.
BTW, in Common Lisp, namespaces are not tied to classes at all.
Big mistake.
Why? I'm curious.
Encapsulation, among other things I've already explained in previous p=
osts.
You can have encapsulation even if packages are not tied to classes,
since packages have the concept of internal ("private") and external
("public") symbols.
You can have THAT level of encapsulation in C, FFS. (Using or
withholding "extern" and with judicious omissions from the .h file.)
I don't know C enough to comment on this.
Which, due to the generality of symbols,
automatically can make private/public anything - classes, functions,
variables, anything that can be named by a symbol.
But only at the granularity of whole packages. Not single classes or
smaller scales, aside from what Series elsewhere described as the
"natural encapsulation" of functions whereby ordinarily changes to the
function's body that don't change its signature or semantics don't break
code elsewhere in the program.
The "natural encapsulation" is only natural in Java, C++ and languages
with a similar kind of OO. It's not the only possibility. In Lisp, the
natural encapsulation is at the package level; in another language, it
might be at the module level; and these are certainly not all the
possibilities. Each has its pros and cons. I find the Lisp one
convenient *in Lisp* because Lisp has first-class symbols. In Java the
same thing would be messier - it's more natural in Java that classes
create namespaces.
In Lisp you have
package.symbol
and stop. This means there's no difference between
package.className and
package.genericFunctionName
That's when you're referring to just one of those. I'm discussing the
commonplace situation when you're referring to one of each. Then you
need to specify 2 packages. Somehow.
You *need* to specify 2 packages only if you have imported *neither*
symbol in your package, which is quite unusual. Else, you can use
symbols unqualified, and this is the general case.
Verb collisions are the issue. So many things will have close methods,
for example. If you use several of them in one area, you'll only be able
to import one and you'll have to FQ the others. Unless of course the
library designer did as someone else recently suggested here and cracked
open his thesaurus. (Yuk.)
Sure, this is a possible problem. I know you probably won't believe
me, but it does not happen frequently; no more than it happens in Java
with two classes of the same name.
This is the flipside to the many-times-more-nouns-than-verbs thing. A
lot of verb collisions occur among a small set of especially
frequently-used verbs. The power-law scaling of word frequencies serves
to amplify the difference of nearly an order of magnitude in the numbers
of nouns vs. verbs.
Or you can define a general case and specialize only the cases that
interest you, only for the "pairs" (or tuples) that make sense for th=
e
problem at hand.
The (foo foo) (bar bar) syntax does not allow to "define a general
case", save if all project objects inherit from some class, say Object=
,
and you put in a dispatch for (Object Object) (Object Object), and thi=
s
respects inheritance, so any call lacking a more specialized applicabl=
e
dispatch with arguments both inheriting from Object will get that disp=
atch.
Then the real fun begins: enforcing "NO CLASSES NOT DERIVED FROM
OBJECT!!" everywhere in the project. Unless CLOS already has an
omni-superclass like Java's java.lang.Object class.
Guess what? It has, it's the class named T.
They have a problem with code readability or something? That reads like
true, a temporary, or, worst, a generic type parameter, not a class name.
T is the canonical way to represent true. Historically it has also
been used to designate the root of the type hierarchy - the type of
all objects - and naturally it has been used to designate the root of
the class hierarchy, too. Don't ask me why. Surely it does no harm :)
Nobody mentioned method combinations, and I won't now - already too
many things being discussed - but that's another of the big strengths
of CLOS.
I'll interpret this as "another of the big headaches of CLOS". It sounds
like one of the biggest headaches of Lisp is the sheer amount of stuff,
special forms, &c. that you'd have to learn to be able to read other
peoples' code written in the stuff. OK, perl is much worse, but still.
No comment.
Maybe then it's not information about class X, but about classes X, Y
and Z. It is precisely this kind of thing that is easy to express in
CLOS
It is easy to express precisely nowhere. CLOS lets you lump everything
to do with X, Y, and Z together in the same place. So? This might easi=
ly
end up meaning pretty much the whole project. So much for namespaces,
nevermind encapsulation, which we'd already given up on ages ago.
A fantasy example:
(defgeneric print (object medium))
(defmethod print ((object text) (medium terminal)) ...)
(defmethod print ((object text) (medium laser-printer)) ...)
(defmethod print ((object image) (medium terminal)) ...)
(defmethod print ((object image) (medium laser-printer)) ...)
This is easy to express in CLOS, not that easy to express in Java or C+
+ or Python or any other single-dispatch OO system.
Eh, Java has already expressed this and very easily. Look at the Java2D
API. Corresponding to "text" and "image" we have Shape and subclasses,
which can draw themselves onto a Graphics2D (polymorphically). And
Graphics2D is gotten by various means, but is itself a polymorphic class
that knows how to turn turtle-graphics commands using the generic
Graphics2D interface into results on particular media. If you're
prepping a print job you'll draw on one subclass of Graphics2D, if on a
window you'll get another. No terminals though, Java2D is all about
bitmapped graphics.
For your example a Java programmer might make a Printable interface with
a printOn(Medium) method specified. Implementations would call Medium
methods such as outputString(String), toggleBold(), or whatever was
supported. VT100Terminal would send appropriate escape sequences for
things like toggleBold() and the string for outputString(String) to
System.out. LaserPrinter would send suitable printer control commands to
the parallel port.
Then you could have aText.printOn(aVT100), etc.
I don't know what you were hoping to do with images there; error when
the target was a terminal and print on a printer? That might require
Java2D and adding a getGraphics() to Medium, returning null for
VT100Terminal -- an OOB not-available value printOn would check for.
Image might attempt to gracefully degrade showing a Lynx-style "[IMAGE]"
or attempting to cruft up an ASCII-art approximation using some
algorithm or another, or just complain with an exception. Alternatively,
Medium could throw a GraphicsUnsupportedException or similar from
getGraphics() instead of returning null if the target didn't support
graphics.
All of this with single dispatch.
Where I find myself most wanting double dispatch is when there's two
type hierarchies that need some combined operations, but there's no
natural way to orthogonalize the operations. With most forms of drawing
or printing, though, you can turn the document into a series of
primitive operations of some kind at the document end, and implement the
primitive operations on the medium end. The Graphics2D class is a good
one to check out as its API contains such a set of primitive operations.
http://java.sun.com/javase/6/docs/api/java/awt/Graphics2D.html
A lot of the methods are inherited from
http://java.sun.com/javase/6/docs/api/java/awt/Graphics.html
Heh, Graphics2D is really an example of what I was saying :) it has
drawPolygon, drawOval, drawText, and a few other drawSomething. What
if I wanted it to draw the cool chart generated by my favourite
library? I'd have to transform the Chart in an Image first, or write a
drawChart(Chart, Graphics2D) utility method somewhere and use the
drawXXX primitives there. In CLOS, I would simply define a new method
specialized on Chart and Graphics2D, and it would be indistinguishable
from built-in methods that deal with polygons, text, and images.
One option is to arbitrarily make one of the two classes "more
important":
interface Printable {
public void printOn(Medium medium);
}
class Text implements Printable {
public void printOn(Medium medium) {
if(medium instanceof Terminal) {
...
} else if(medium instanceof LaserPrinter) {
...
} else {
throw new IllegalArgumentException("Don't know how to print=
" +
this + " on " + medium);
}
}
}
...same for Image...
Bletcherous. See above for the alternative that avoids switch, enum,
instanceof, or any other similar kludge.
Another option is to concentrate the printing code in a third class:
abstract class Printer {
public void print(Printable object, Medium medium) {
if((object instanceof Text) && (medium instanceof Terminal)) {
...
} else ... //you get the idea
}
}
Neither option is good imho, since both force you to explicitly code
dispatch tables and manually maintain them - exactly what you DON'T
have to do with CLOS.
Except that a) I just showed how to avoid doing so -- in this case at
least -- in Java, and b) Anonymous C Lisper does exactly that with CLOS
to judge by his one major post containing sample Lisp code.
But if you confuse your personal preference with objective
truth, and write that "truth" in public, I - we - feel the need to
correct your claims.
That statement makes several insulting and false insinuations about me=
..
I'm sorry, but I genuinely think [repeats insults]
Think what you want, but please restrain the urge to blurt out uncivil
things in public. Weren't you taught any manners as a child?
I don't think saying someone is in error about something is insulting
him.
(Subclassing a class doesn't change the base class. But subclassing =
a
class creates a new class that has its own name and is in its own
package. Adding a method to a generic function does not create a new
generic function that has its own name and is in its own package. It
adds to the existing generic function instead, thus changing it.)
Subclassing a class DOES change the class, if you intend "class" as
the set of all possible instances of that type.
Sophistry. It doesn't change the base class itself, and indeed, code
that uses the base class won't be affected unless someone passes it a
reference to a derived-class instance.
Then, it changes the class
It does not.
Since there aren't separate "base" and "derived" generic functions in
Lisp, however, the above does not apply there, and the "base itself" I=
S
changed.
Uh-oh.
*in this context*, the following analogy more or less holds:
generic function --> abstract class
method --> concrete class
more specific method --> concrete subclass
Except when you subclass a class, you're making an altered copy in
essence; when you add to a generic method, you're fiddling with the
original, not a copy, and maybe it wasn't "yours".
See above re: MY:frobnobdicate with YOUR:quux.
If I write MY:frobnobdicate specialized on YOUR:quux, I'm not altering
the behavior of MY:frobnobdicate specialized on other objects. I'm
adding a possibility, not changing behavior. If you call
MY:frobnobdicate with MY:quux, the behavior is the same whether I have
defined the YOUR:quux version or not. Although methods are registered
in a "global" place - their generic function - each method is a thing
of its own, much like a derived class is distinct from its base class.
In pseudo-Java:
public abstract class MyGenericFunction<T1, ..., Tn> {
public abstract Object call(T1 arg1, ..., Tn argn);
}
public class MyMethod1 extends MyGenericFunction<my.Foo, ..., my.Bar>
{
public Object call(my.Foo arg1, ..., my.Bar argn) {
...
}
}
public class MyMethod2 extends MyGenericFunction<your.Foo, ...,
your.Bar> {
public Object call(your.Foo arg1, ..., your.Bar argn) {
...
}
}
If I add MyMethod3, the behavior of 1 and 2 does not change, yet I can
get different results from an instance of MyGenericFunction (when it
is an instance of MyMethod3).
The generic function is not changed any more than the abstract class
is.
Sure it is. The abstract class still contains exactly the same methods.
The generic function contains new ones. Viewing both as lists, one grew
and one didn't.
Viewing one as a list and the other as a tree (class hierarchy), both
grew.
This is not specific
to Lisp, conventions are needed in every language that allows more
than one way to do the same thing (IMHO, every serious language).
But the more ways there are to do a thing, the worse it gets; indeed,
you can easily get a combinatorial explosion like the one that nuked
perl as a viable language for large-scale development efforts.
That's subjective, and I won't debate it here. I prefer the there's-
more-than-one-way-to-do-it philosophy, but it's just personal
preference.
It's a preference that suggests you tend to work alone or on a small
team. Working on a large team results in there's-too-many-ways-to-do-it
being a very real possibility, with everyone doing the same "it"
differently and everyone having to know every single language feature,
however obscure.
there's-only-one-way-and-it-is-not-optimal is a real possibility, too.
But this is OT.
Without the javadocs Java's standard library might plausibly have caused
some trouble of a similar nature by its sheer size. It helps though that
there's generally only one or a few clearly-near-optimum use patterns
for a given job, and that everything (mostly) has descriptive names too.
ListIterator and MessageDigest are names that convey quite a bit about
what they do; defgeneric and #:cl #:foo somewhat less so. :)
defgeneric DEFines a GENERIC function.
cl is shorthand for common-lisp, and foo... well, it's foo :)
I have misunderstood nothing. According to you, Common Lisp programs=
are
organized at the whims of individual programmers. Not conducive to
scaling up to a large development team, mind you.
Only if you let individual programmers do it at their whim. Just as i=
f
there wasn't no convention among Java programmers in a team on how to
name packages, for example.
Java needs relatively few conventions BESIDES how to name packages. Li=
sp
will need a convention the documentation of which would consume 74,172
trees (plus or minus five) to print and massing 11.3 tons. Looking up
anything in the convention will take 1 month, 17 days, 3 hours and 11
minutes, plus or minus one, assuming it's found on average halfway
through. The one upside is that it will make one hell of a paperweight
or doorstop.
Interesting
Well, we think that there is a fundamental difference between diffe=
rent
sorts of close and prefer to make sure they have different names.
So instead of everything you can "close" having a "close" method, yo=
u
end up "close"ing one type, "shut"ting another, "slam"ming a third,
"plug"ging a fourth, and onward to increasingly unintuitive and
unguessable names? Wow, that must REALLY help code readability.
No.
Yes. Thomas said you'd not use "close" for everything that closes, whi=
ch
means Fun With Your Thesaurus time folks!
Which topic came up again; see above.
streams:close(file-stream)
This vs. Java's fileStream.close(). And you guys said *Java* is bloate=
d
and looks like vomit?!
I personally don't think Java the language + standard library is
generally bloated. I believe some widely-adopted practices, and some
widely-used libraries, do favor bloat in Java.
I'd agree with that assessment. But I didn't say *you specifically* said
Java code looked like vomit. Certainly *one* of you did, though, a few
days ago, and others appeared to hold comparable opinions.
I also don't think Java looks like vomit, if you don't make it look
like that. Though I do think that expressing some things in Java is
not as easy as in Lisp.
Including especially the things that will fry your coworkers' brains, no
doubt. (Though you can be pretty evil with the reflection API, static
initializers and constructors with side effects, asserts with side
effects, ClassLoader tricks, and so forth.)
For what I know, both Java and Common Lisp have been accused of being
"bloated" for a period of their respective history.
Java's library and Common Lisp's object system, I'll bet. But users
don't have to learn the whole library and do have to learn the whole
object system...
I can only say - only personal experience, no scientific data - that
writing both Java and Common Lisp, I generally find Common Lisp more
expressive and more readable, even if I have much more experience in
Java than in Lisp. This might simply mean Lisp is more close than Java
to my own way of thinking.
Subjective statements like that cannot resolve many of the questions
here; at worst they might fan the flames, though maybe with "Lew"
apparently bowing out of it the worst of those are over.
I actually don't think most people here disagree with my main thesis,
which is that Java and Lisp have relative tradeoffs, with neither
invariably superior to the other. The sticking points all seem to
involve specific things claimed to make Lisp superior at one time or
another by only a handful of the most extreme pro-Lisp partisans, and
why these features don't *actually* do so.
Mind you, I usually refrain from saying something is absolutely
"superior" because there are really many dimensions to be considered,
but I do think Lisp has some key ideas that are superior to what I
find in other languages. And yes, one of them is macros :)
I also think Java has a lot of nice features which probably many
lispers don't know about. Built-in serialization is one of those, for
example, as is the nice integration of URLs into the language, the
collections framework, ...
Maybe that's the reason I'm using, and sometimes contributing to, a
Lisp implementation running on the JVM... :)