what would you people make of the above statement ? Has anyone here
used Lisp *and* Java on major projects ? If so would you say this
sounds off-base or correct ?
thanks,
--dcnstrct
> "FACT: Java has no first-class functions and no macros. This results in
> warped code that hacks around the problem, and as the code base grows,
> it takes on a definite, ugly shape, one that's utterly unique to Java.
> Lisp people can see this clear as day. So can Python folks, so can Ruby
> folks. Java people flip out, and say "macros are too much power",
> or "what do u mean i dont understand u" or "fuck you, you jerk,
> Lisp will NEVER win". -- Steve Yegge, 2006-04-15
>
>
> what would you people make of the above statement ?
Flame bait by a potty mouth?
He makes a grossly wide statement about java coding and then creates a
fictional Java programmer he can make look myopic.
> Has anyone here
> used Lisp *and* Java on major projects ? If so would you say this
> sounds off-base or correct ?
>
>
Lisp is a fine tool. Java is a fine tool. C++ is a fine tool. Even Python
can be a fine tool. Sense a pattern? They are tools not religions! First-
clas function(and Objects) are nice. So are Closures. But you can get by
without them. It is just easier when you don't have too.
Pick the one your can do the current one the best in. Note that I did n't
say the best one for the job. If you do not know functional programming,
learn one but that might not be the best way to tackle a project.
C and C++ have done fine up to now lacking FCF. Java does have anonymous
and inner classes that fill a small but significant part of the need.
you need to pick the right tool for a job and as the saying goes, its a
poor craftsman who blames his tools.
OB
I'm not sure what a first-class function is, but assuming
it's a function in the global namespace I for one would
think such unnecessary, as long as one knows the relevant
namespace. For example, Math.cos() is acceptable if
one knows where Math is -- and that's what documentation
is for.
As for macros -- one can hack around that lack, if lack
it be, in various ways; the standard one nowadays would
be m4. However, most usages of macros are better served
by functions. I'll admit that Lisp's ideas of macros is
not something I'm that familiar with beyond the fact that
they don't allow C macro abuses.
I'm mildly surprised the individual did not mention
the elephant in the room: operator overloading, along
with the issue of Java's primitive/Object dichotomy.
The former leads to constructs that get unwieldly, though
it's a tradeoff. The latter is an annoyance to purists
but most people shrug it off.
An apply() taking an arbitrary function call would be nice.
The closest I can get is something along the lines of
List apply(Collection c, Method m, Object[] extraparams)
throws Exception
{
List results = new ArrayList();
for(Iterator i = c.iterator();i.hasNext();)
{
results.append(m.invoke(i.next(), extraparams));
}
return results;
}
This looks workable but cumbersome, and is probably slow.
At that, Java is an improvement over C/C++, which has used
various methods to attempt what Lisp does almost naturally.
However, C++ has the advantage of templates, which allows
for STL's for_each() construct:
template <typename Q, /* ... */ >
class arbFunc
{
public:
void operator()(Q q) { /* ... */ }
}
arbFunc</*...*/> func(/*... */);
std::for_each(ibegin, iend, func);
Slightly cumbersome but powerful if used properly. Of
course most would probably just use the for loop instead:
for(/*...*/::iterator i = begin; i != end; i++)
func(*i, /* ... */);
so one gets a for loop "pepper" effect.
There's also the null versus "" versus "0" trichotomy. This
can lead to some weird-looking code:
void processArgs(String[] args)
{
if(args != null) for(int i = 0; i < args.length; i++)
{
if(args[i] != null && args[i].equals("-help"))
{
/* ... */
}
if(!"-help".equals(args[i]))
{
/* ... */
}
}
}
The first is straightforward but awkward. The second is slightly
unnatural, although logical enough.
As for "0": one might make a case that
void routine(String s) { int x = s; /* ... */ }
makes sense (e.g., PL/1); in Java one has to go through various
gyrations:
void routine(String s) {
int x;
if(s == null) x = 0;
else if (s.equals("")) x = 0;
else try {
x = Integer.parseInt(s);
}
catch(ParseException e)
{
x = 0;
System.out.println("Error: " + e.getClass().getName() + ": "
+ e.getMessage());
}
/* ... */
}
> This results in
> warped code that hacks around the problem,
An example of such "warped code" would be nice.
> and as the code base grows,
> it takes on a definite, ugly shape, one that's utterly unique to Java.
> Lisp people can see this clear as day. So can Python folks, so can Ruby
> folks. Java people flip out, and say "macros are too much power",
> or "what do u mean i dont understand u" or "fuck you, you jerk,
> Lisp will NEVER win". -- Steve Yegge, 2006-04-15
>
>
> what would you people make of the above statement ? Has anyone here
> used Lisp *and* Java on major projects ? If so would you say this
> sounds off-base or correct ?
It's off-base but not that off. Still, which language is winning?
It's not Smalltalk or LISP. Maybe next iteration as computers
continue to increase in power. I don't know at this point. :-)
>
>
> thanks,
> --dcnstrct
>
--
#191, ewi...@earthlink.net
Windows Vista. Because it's time to refresh your hardware. Trust us.
> what would you people make of the above statement ? Has anyone here
> used Lisp *and* Java on major projects ? If so would you say this
> sounds off-base or correct ?
It's off-base because what he's really attacking is objects, and
reliability, both of which - in the context of Java -
functions-as-objects and silly macro stuff would negate: What would be
the point of class A calling a method of class B without the context
of class B?
Java "solves" the first problem via the Strategy, Command and other
patterns, like any proper OO language would. The second "problem" -
macros as Lisp understands the concept - is in effect code "compiled"
at evaluation time, which is a big no-no in Java.
I think the quoted bit is - like 99% of all articles attacking Java -
a tirade written by someone envious of how Java stole the thunder of
their pet language, whether it's Lisp, Smalltalk, Prolog, Beta, C++,
Objective-C, C+@, D, Ocaml, Haskell, C#....
For Lisp, you could also say Python and Ruby stole its thunder. There
are no more avenues left to conquer for such academic niche pets.
If one is going to go that route one will also need lambda
closures. From what little I understand thereon, in Java
one would have to have a syntactic construct that might
look like
Closure f = obj.g(k,<1>,<2>);
as a local variable. f would then require two parameters
and look much like a function call. k would have the same
restrictions as passed-down members/final locals in anonymous
classes do today.
Other syntactical possibilities ensue; for example, one
might simply declare closure as a primitive keyword type,
and have it act much like a function or implicit function
pointer:
closure int(String n1, String n2) f = obj.g(k,n1,n2);
There would be two objects in the closure: 'this' and 'obj'.
obj would have to be a final local variable or a member.
In this context a lambda closure with zero passed-down
values would be your "first-class function", I think.
Closures would be reassignable and templatizable, and
of course used as values, leading to some ugly syntax
without typedefs, but one could contemplate something
along the following lines:
int(String k1, String k2)(int k1, int k2) f(String p, int q, int r)
{
}
which would be a function returning a closure taking two
int args, which itself would be a closure taking two
String args, finally returning an int.
One can also pass in closures, in a fairly obvious fashion:
int f(int(String k1, String k2) g, int) { int k = f("x","y"); /* ... */ }
One might also contemplate templatized parameters, though
I'm not sure what the exact syntax would look like, though
the following might work:
template <T> int f(int(T k1, T k2) g, int) { /* ... */ }
or
int f(template<T> int(T k1, T k2) g, int) { /* ... */ }
The main issue is proper scoping of T within the function block.
In the former, a call might look like
f<int>(g, 2);
but in the latter a call would have to be
f(g<int>, 2);
if g is a templatized closure, and
f(g, 2);
if g is not.
>
>>but assuming it's a function in the global namespace I for one
>>would think such unnecessary, as long as one knows the relevant
>>namespace.
>
> Values by themselves are anonymous and thus the notion
> is independent of namespaces. However, there is a connection
> insofar as values usually can be bound to names.
>
> Values are run-time entities, names are source-code entities.
>
>>As for macros -- one can hack around that lack, if lack
>
> Here is an example, where I use macro to emulate a kind of
> first-class function objects.
If one has true first-class functions why use macros at all?
>
> My Java-program following below in the style of functional
> programming shows how to implement and use an »apply«
> operation that will take a collection »c«, filter it by a
> predicate, such as
>
> PREDICATE( java.lang.String, e, e.indexOf( 'e' )> 0 )
boolean(String, int) v = /* ... */;
if(v("abc", def)) { /* ... */ }
>
> and then send the values passed to an arbitrary operation like
>
> ACCEPT( java.lang.String, s, java.lang.System.out.println( s ); )
See e.g. java.io.FileFilter for an interface that
can accomplish a similar task. The syntax is admittedly
not as clean, though.
>
> It could be written without macros, they just server to
> make it more readable (or "less" readable, as some might say).
The main problem I have with macros is that they scramble things.
Properly used, they can clarify a program greatly. Improperly,
well, you're no doubt aware of the C Obfuscation contest; some
of the entries are ... interesting. :-)
>
> Sun should have defined interfaces such as "Function" and
> "Predicate" long ago to support interoperability between
> different Java libraries preferring such a style.
>
> This style, passing parametrized blocks (in other words:
> "first-class functions"), by the way, is inherent to /object
> oriented programming/ -- not only functional programming. That
> means "object oriented programming" in the original sense (the
> Smalltalk-sense), see:
>
> http://daitanmarks.sourceforge.net/or/squeak/squeak_tutorial-2.html
>
> ------------ cut here
A codeblock could be a closure as well, and in fact a function
could be construed as a readonly closure followed by its codeblock.
void process(boolean(String p1, int p2) p, int v)
{
if(p("Test 1", v)) { /* ... */ }
}
and then one might call this with:
process( { return false; }, 2)
or, if one needs to reference the parameters for some reason,
one can use a rather ugly cast:
process( (boolean(String p1, int p2)) { return p1.equals("Duh"); }, 2)
The advantages of this particular notation over anonymous
classes implementing interface are not entirely clear;
in Java, one could simply declare:
interface Q { public boolean predicate(String p1, int p2); }
void process(Q q, int v) { if(q.predicate("Test 1", v) { /* ... */ } }
process(new Q() {
public boolean predicate(String p1, int p2) { /* ... */ }
}, 2);
>
> interface Function<Range,Domain>{ Range of( Domain value ); }
> interface Predicate<Domain> extends Function<java.lang.Boolean,Domain> {}
>
> --- PREDICATE
> --- a predicate object representing a predicate of one parameter
> --- @param $1 The type of the parameter of this predicate
> --- @param $2 The name of the parameter of this predicate
> --- @param $3 The value of this predicate for the parameter
> $define PREDICATE new Predicate<$1>()
> { public java.lang.Boolean of( final $1 $2 )
> { return $3; }}
>
> interface Accept<O>{ void accept( O object ); }
>
> --- ACCEPT
> --- an acceptor object acception an argument
> --- @param $1 The type of the parameter of this acceptor
> --- @param $2 The name of the parameter of this predicate
> --- @param $3 The statements of this acceptor
> $define ACCEPT new Accept<$1>()
> { public void accept( final $1 $2 ){ $3; }}
>
> public class Main
> {
> public static <E> void apply
> ( final java.util.Collection<E> c, final Predicate<E> p, final Accept<E> acceptor )
> { for( final E e : c )if( p.of( e ))acceptor.accept( e ); }
>
> public static void main( final java.lang.String[] commandLineArguments )
> { final java.util.Collection<java.lang.String> c =
> java.util.Arrays.asList( "alpha", "beta", "gamma", "delta" );
> apply( c, PREDICATE( java.lang.String, e, e.indexOf( 'e' )> 0 ),
> ACCEPT( java.lang.String, s, java.lang.System.out.println( s ); )); }}
>
> beta
> delta
C++, perhaps, unless C now accepts the construct
int m(int, int);
int (*p)(int, int);
m = p;
p(1,2);
which last I checked it didn't but that was awhile ago;
one must use
(*p)(1,2);
(C++ has no problem with it.)
>
> #include <stdio.h>
>
> int f0( int const x ){ return 2 * x; }
> int f1( int const x ){ return 3 * x; }
>
> int( *g( int const x ))( int const x ){ return x > 0 ? f0 : f1; }
>
> int main( void ){ printf( "%d", g( 1 )( 2 )); }
>
> 4
>
> C even allows the definition of a function "returning
> itself":
>
> #include <stdio.h>
>
> void * f( int const x ){ return f; }
> int main( void )
> { printf( "%d\n", f );
> printf( "%d\n", f( 0 )); }
>
> 268440528
> 268440528
>
But C does *not* allow:
int f(int, int);
int g(int, int);
int h(int, int);
h = flag ? f : g;
h(1,2);
though one can work around it in C++ with an ugly pointer:
int (*h)(int, int);
h = flag ? f : g;
h(1,2);
This is mostly because
int f(int, int) { ... }
is vaguely similar to
int (*f const)(int, int) = { ... };
although the latter is not proper C++. In assembly, f would
be considered an immobile label.
C/C++ arrays are also labels; Pascal allows
type tarray: array[1..10] of integer;
var a: tarray; b: tarray;
begin
a := b;
end.
but trying to do similar things in C++ will not work:
typedef int ary[10];
ary a, b;
int main() { a = b; return 0; }
test.C:3: error: ISO C++ forbids assignment of arrays
I had to do some reading up on all this because at first I didn't fully grasp
what was meant with macro's. So a google gave me a nice page
(http://www.apl.jhu.edu/~hall/Lisp-Notes/Macros.html) and from what I make out
of that page I think he's wrong.
A few quotes: "Macros in Lisp provide a very powerful and flexible method of
extending Lisp syntax.", and: "'(defmacro Square (X) \ '(* ,X ,X))' This means
that wherever the pre-processor sees (Square XXX) to replaces it with (* XXX
XXX). The resultant code is what the compiler sees." (no '' and \ means cr/lf).
Isn't this basicly what a plain method does? If I wish to extend my Java syntax
(and I do) I can easily write packages of my own which to do just that. And
Java makes writing and using your own packages (or perhaps "extensions"?)
extremely easy, this goes double when using IDE's like Netbeans. Heck, one of
the first things I did when I started out with Java was creating a project
"catslair tools" consisting of packages like org.catslair.cli and
org.catslair.gui which, as the name denotes, classes to help me with cli or gui
based issues.
Need to grab input from stdin?
import org.catslair.cli.GetInput;
...
String userinput = ReadInput();
(ps: I know; 'readInput()' would have been a better name for the method, but
when I wrote this up I hadn't read about naming conventions yet).
Second; 'first class functions'. That was a little harder for me to grok, but I
ended up on the Lisp site itself
(http://www.lisp.org/table/glossary.htm#first-class) and from what I can make
out of that I wonder if Java's annotations aren't very much alike here. This is
confusing for me, but when I look at the description: "A language supports some
data type as first-class when the objects of the type can be created and used
as data at run time." and considering how everything in Java is defined as an
object I think his point isn't valid. Granted: I have little to no experience
on the Lisp front but it would seem that there isn't really much difference.
> This results in warped code that hacks around the problem, and as the code
> base grows, it takes on a definite, ugly shape, one that's utterly unique to
> Java. Lisp people can see this clear as day. So can Python folks, so can Ruby
> folks.
I'd really like to know what the problem is then. IMO there is absolutely no
need what so ever for any "warps" to "overcome this problem" simply because as
far as I can see the problem isn't even there.
When looking at my above example to "extend the Java syntax" you'll see its
nothing out of the ordinary. This is basicly common Java programming.
> what would you people make of the above statement ?
I think he has clue when it comes to Java. If the comments he quoted are indeed
from Java programmers then I have a hunch that they too saw that he's trying to
argue and present problems which simply aren't there. And considering the tone
of his whole argument I can understand if some people get annoyed about it.
--
Groetjes, Peter
.\\ PGP/GPG key: http://www.catslair.org/pubkey.asc
I don't think he's attacking objects. For one he's promoting Common
Lisp which has an Object system which in my opinion is better(more
flexible) than java's. He also mentions Ruby which is a completely OO
language. Ruby has these first class functions(closures) and puts them
to good use without giving up its object orientated nature.
> Java "solves" the first problem via the Strategy, Command and other
> patterns, like any proper OO language would.
In Java, you can use the Command design pattern for example to ensure
the opening and closing of a file, database transaction, or other
resource. The problem is that this burdens the code with the useless
overhead of an anonymous inner class and callback method; in addition,
there is the distracting rule that variables passed into the anonymous
Command class must be declared final. And to ensure that logic always
executes at the end of a code block, the whole thing must be wrapped
with try{...}finally{...}.
In Ruby or other languages with first-class-functions, you can wrap any
function or code block, with no need for anonymous classes or method
definitions. In the following example the file opens, then closes
after the write() method. No code is needed beyond the "transaction"
method and the code block
File.open("out.txt", "a") {|f|
f.write("Hello")
}
This code is super sleek compared to what you would have to do in java
to achieve this same behavior. The lisp code is for this is not much
larger than the Ruby code. First class functions save the day, they
keep code smaller and easier to understand and maintain.
> The second "problem" -
> macros as Lisp understands the concept - is in effect code "compiled"
> at evaluation time, which is a big no-no in Java.
Sure its a big no-no in java, I think thats what Steve Yeggie is trying
to say. Java is less powerful because you cannot create code that
expands to other code like you can in Lisp.
> I think the quoted bit is - like 99% of all articles attacking Java -
> a tirade written by someone envious of how Java stole the thunder of
> their pet language, whether it's Lisp, Smalltalk, Prolog, Beta, C++,
> Objective-C, C+@, D, Ocaml, Haskell, C#....
Sure no doubt he's envious, but does that mean the things he says about
java are incorrect ? I don't think so.
> For Lisp, you could also say Python and Ruby stole its thunder. There
> are no more avenues left to conquer for such academic niche pets.
Pretty much all of the super cool stuff in Ruby came over from Lisp
(and some from smalltalk). This got me interested in Lisp. I've
discovered that lisp provides super useful stuff (like macros) that
even Ruby lacks, and Lisp is a hell of alot faster!
--dcnstrct
Now, I have read this very carefully many times and think I finally get the
whole idea. However it does raise another question with me: doesn't this
basicly allow for the "hideous spaghetti like" code the OP's quote referred to,
but simply presents a method to better masquerade it?
I've seen it happening in Java quite a few times that I used almost the same
routine. In those cases I resort to recursing and, if needed, making the
routine in question more modulair. I think thats a whole better approach than
to merely use some macro language to copy the entire routine around your entire
program wherever you need it.
/unless/ ofcourse we're not talking about actually copying the entire stuff but
treating it like a "sort of method". Still, even then I can't picture any
situation which would make this approach feasible.
<http://en.wikipedia.org/wiki/First-class_function>:
In computer science, a programming language is said to support first-class
functions if it treats functions as first-class objects. Specifically, this
means that functions can be created during the execution of a program,
stored in data structures, passed as arguments to other functions, and
returned as the values of other functions.
These features are a necessity for the functional programming style, in
which (for instance) the use of higher-order functions is a standard
practice. A simple example of a higher-ordered function is the map or
mapcar function, which takes as its arguments a function and a list, and
returns the list formed by applying the function to each member of the
list. For a language to support map, it must support passing a function as
an argument.
<http://en.wikipedia.org/wiki/First-class_object>:
In computing, a first-class object (also -value, -entity, -citizen), in the
context of a particular programming language, is an entity which can be
used in programs without restriction (when compared to other kinds of
objects in the same language). Depending on the language, this can imply:
* being expressible as an anonymous literal value
* being storable in variables
* being storable in data structures
* having an intrinsic identity (independent of any given name)
* being comparable for equality with other entities
* being passable as a parameter to a procedure/function
* being returnable as the result of a procedure/function
* being constructable at runtime
For example, in C, it is not possible to create new functions at runtime,
whereas other kinds of object can be created at runtime. So functions in C
are not first-class objects; sometimes they are called second-class objects
because they can still be manipulated in most of the above fashions (via
function pointers). Similarly, strings are not first class objects in
FORTRAN 66 as it is not possible to assign them to variables, whereas
numbers can be so assigned.
--
--Tim Smith
Before going down that path, one should first consider Greenspun's Tenth Rule:
Greenspun's Tenth Rule of Programming: any sufficiently complicated C or
Fortran program contains an ad hoc informally-specified bug-ridden slow
implementation of half of Common Lisp.
--
--Tim Smith
I believe on reason the variables must be declared final is to reduce
bugs resulting from the misconception above. You don't actually pass
variables around. You pass the objects that the variables point to. But
people might incorrectly think that the variable itself is being passed
around, and thus expect any changes made to the variable to be visible to
the anonymous class. If the variable is made final, there's no chance for
this error to occur.
- Oliver
Perhaps so, but I was speaking of how Java works currently, as opposed
to how Java might work if it had closures.
- Oliver