Re: new Java lambda syntax
On 13/09/2011 4:29 PM, Tom Anderson wrote:
Basically, lambdas can be either method-like (they return values from
themselves) or statement-like (they return values from their enclosing
methods).
Smalltalk blocks can do both. The usual return operator ^foo returns foo
from the enclosing method, but the if the block doesn't do a nonlocal
return or exception throw of any kind it evaluates to a value which is
equal to that resulting from the last expression inside of it. So:
[:a | ^(a + 1)] value: 3
returns 4 from the enclosing method;
[:a | (a + 1)] value: 3
returns 4 into the enclosing scope; and
[:a :b | b ifTrue: [^a] ifFalse: [a]] value: 3 value: x
returns 3 from the enclosing method if x is true and evaluates to 3
otherwise.
Yes, this can be confusing. But blocks are used to implement ifs, loops,
and other control structures so this ability seems necessary,
particularly to have any way to break out of a loop early, say because
you found something. It also means that Smalltalk methods and blocks are
fundamentally different things, whereas the Java lambda proposal
generates methods under the hood, rather than some new, separate
freestanding-function category of thing.
Lambdas are method-like in all the current dynamic languages
(AFAIK - they certainly are in Smalltalk, Python and Javascript).
Um, not really; see above. Smalltalk blocks are objects, and the value:,
value: value:, etc. methods can be called on them to run their code, but
they aren't themselves methods. In particular, they are different
classes in the Smalltalk object system: BlockContext vs. CompiledMethod.
If you meant though that they can evaluate to a value, that's true. On
the other hand if you meant they can't also nonlocal-return, that's not
true.
Incidentally, you can implement the behavior of Smalltalk blocks in even
Java 1.1, with a class something like:
public class BlockReturn extends RuntimeException {
public final Object value;
public BlockReturn (x) { value = x; }
}
public interface Block {
Object do (Object x);
}
....
public static Vector forEach (Vector i, Block b) {
Vector res = new Vector();
for (Enumeration e = v.elements(); e.hasMoreElements();) {
res.addElement(b.do(e.nextElement()));
}
return res;
}
....
public Object nullOrHashCodes (Vector v) {
try {
return forEach(v, new Block() { public Object do (Object x) {
if (x == null) throw new BlockReturn(null);
return Integer.valueOf(x.hashCode());
}}
} catch (BlockException b) { return b.value; }
}
That last should end up taking a vector and returning null if it
contains nulls and otherwise a vector of Integers equal to the hash
codes, in order, of the objects in the input vector. Obviously, it's not
idiomatic Java 1.1, let alone idiomatic Java SE 6 where you'd just use a
foreach loop over a List, but it is isomorphic to how you'd do something
like that in Smalltalk, where it would be something like
coll collect: [:x | x ifNil: [^nil] ifNotNil: [x hash]]
The rationale was that enclosing any given wodge of code in a lambda
definition immediately followed by an invocation of that lambda would
not change the behaviour of the code. This seemed dubious to me, and
would have led to very weird programming experiences. It would, though,
have enabled a wider range of funky control structures implemented on
top of lambdas, which some people are very keen on.
Smalltalkers, probably. Possibly Lispers too, though in Lisp lambdas
can't nonlocally return; Lispers implement new control structures using
macros instead, at least in the case that they need short-circuiting
behavior under some circumstances, such as loops in the bodies of which
there's some kind of break statement analogue possible. But both
Smalltalkers and Lispers are used to being able to implement new control
structures inside of their language.
It's not the first thing they'd probably miss in Javaland, though; the
lack of an interactive transcript would probably top the list, and the
limited reflection/introspection capabilities would come a close second.
It's easy to create new functions, methods, or classes on the fly at
runtime, for example, in those but not in Java. In fact, this is related
to Java's lack of an interactive transcript, in that that degree of
runtime reflection capability is needed to implement such a transcript
if it's to be possible to enter new code or bugfixes at it and not just
run ad hoc tests of existing code. (Not to mention, Windows-based
Lispers tend to use their Lisp repl as a command line shell in
preference to Windows's crummy cmd.exe. But inability to define new
functions at the repl would make it much less useful for such, since you
couldn't write ad hoc scripts and thus couldn't do the equivalent of
sed, awk, and perl hacking at it. For that matter, you'd lack lambdas
too, since using a lambda at the repl implicitly defines a new function,
so even doing something as simple as get a list of files in some
directory, sort descending by size, and return that list would become
impossible given that Lisp sort functions tend to expect the less-than
relation to use to be provided via lambda and tend not to already have a
built-in function that implements a less-than relation on files that is
based on file size.)