Heya! Long time no C! So let’s talk Java.
FreeCell is a moderately recent1 patience card game. It’s got the very desirable property that random deals are quite frequently winnable, which makes it a good candidate for a solo game on the platform. So it’s back to programming like it’s the 90s all over again.
Well, not totally like the 90s. Nowadays, Java is a programming language you can’t code in without an IDE. At least, that’s what everyone told me when I mentioned I just used Emacs. In my defence, text editors handled Java just fine when I first learned it. Back then the standard library was like 6 package names long, most of them taken up by the awt
. Somehow the environment changed and we have to adjust our methods.
I could diss the language all day long, but what good is it in the grand scheme of things if I’m complaining about some old piece of history no one uses any more?
Time to upgrade and rock my own point of view.
So I installed IntelliJ. I’m not sure why I picked this one among the plethora of options. Maybe because it was one of the two put forward by scala-lang.org.2 Maybe because the most vocal JVM proponent at $work happens to use it. Maybe because the name is familiar and I haven’t heard anyone mention Eclipse or NetBeans in too long a time.
As far as first impressions go, I’ll be honest, it’s way too shiny for me. Every word is in a different color, slant and/or background, and I have no idea what meaning to attach to any of them. But hey, I can delete a line with ^Y as if WordStar was still a thing, how could I not feel welcome?
Back to þe good ole SDK. I forked the skeleton as usual: I tend to prefer adding what I need to sieving away all the bloat I don’t, chunk after chunk. For some reason the default setup still complains about there not being a logo.png
, though I can’t seem to find it referenced from anywhere. Weird. I’d file a bug if the tracker’s interface wasn’t so humanely hostile. I think it doesn’t show up on the user’s side, so I’ll dismiss it as bearable.
So, how’s the developer experience now I’m totally not a noob anymore at working around the SDK?
Hard to answer that. Let’s take a look at my support tickets’ log.3
Is there some form of a Java community standard as to how static/final/public qualifiers ought to be ordered?4
I tend to put “final” last.
But I’m no good at deciding what to prioritize between visibility and “static”…
You can feel I’m already feeling the weight of responsibility in the face of the upcoming combinatoric explosion.
Turns out, there is such a thing as a commonly accepted ordering. Thanks @dbdr for coming up with the sonar rule that references the Java Language Specification’s recommendation. Reproduced here for your astonished déjà-vu:
- Annotations
- public
- protected
- private
- abstract
- static
- final
- transient
- volatile
- synchronized
- native
- strictfp
I hadn’t seen synchronized
in the wild since a crazy long time; I didn’t know the JVM model acknowledged volatile
; I hadn’t ever seen a transient
or strictfp
before, but at least I have a vague idea of what the latter might be for.
For some reason I’m not sure I want to hear about5, public
, protected
and private
have an ordering relative to each other. Apart from that, that ordering makes as much sense as any other, and I can adhere to it without feeling too much repulsion.
Is there an Identity functor conceiled somewhere injava.util
?
This one kind of warrants an explanation. I know the support channel needed one.
In this context and without delving too deep in category theory6, functor is synonym for container. Why didn’t I say “container” if I meant “container”? Because I didn’t mean “container”! Ok, that wasn’t helpful. How about: because I’m specifically looking for Identity, which happens to be a functor, so I think of it and enquire about it as such.
Adapting vocabulary is the one major role of support channels. So keeping it down-to-earth Javaesque: the Identity functor is a one-element container. A strict one, mind you, not a vague zero or one, or anything in between, and God knows between null references and Optional
s Java is getting more and more creative on that side as time goes. Once we got past that hurdle, the hotline promptly got back to helpful mode and gave me the answer I was looking for.
No, wait, that’s not what happened.
Once we got past that hurdle, the word out there was a more of a prosaic “who in their right mind would need that‽”
Gee, I don’t know. I mean, I just specifically asked for it, but I probably don’t know what I need. I can’t know what I need, since there I am, asking for information. I definitely don’t know what I need. Nobody in their right mind would need that, as all of the right-minded people around—and I definitely don’t qualify—don’t need it. And, really, I couldn’t need it, as none of the professional Java developers there at the time ever did. And they’ve been around the block—hey, they’re pros. Duh.
For those of you who forgot we were talking about FreeCell: a uniform interface makes for more readable code. Cards can be moved one by one, or entire tableaux7 at a time, and you can’t do that without a uniform interface. Mmm, come to think of it, I’m sure you could if you really wanted to, but trust me, you don’t, you want a uniform interface. “Trust” me? Ha, like anybody would do that! Ok, ok, I concede, you may not want a uniform interface. I want one. My code, my rules.
I hold the opinion it makes the (my) code cleaner: by having the same flow for all moves, I can reveal more of the general context where those moves occur. Higher-level programming, if you will, is not merely a programming language choice issue, but can very much be decided at the code level.
Anyhoo, the simplest answer I got regarding this was @Zorg1’s: use Collections.singleton()
.
Oh, $java_pro, since you appear to be online…
Would you by any chance know of a simple way to get anIterator
on aStack
that would access its elements top to bottom?
In C++, stack
is what’s known as a container adapter. A flyweight wrapper class that delegates storage to an actual container, most likely some form of vector
, and massages outside messages to fit. Java would probably call that a delegate of some sort, but I don’t want to risk using the wrong term for something that isn’t very specifically defined in the JLS.
The point is, a stack is a common data structure, whose interface transcends mere languages and libraries. I won’t insult you by explaining that its metaphor stands for a stack of plates, where only the top plate’s contents are accessible, unless you remove it, in which case a new top plate’s contents are. If you want to know the entire stack’s contents, it’ll happen from the top plate’s to the lowest one, if it exists. Mmm, this is Java, it necessarily exists.
When you allocate a Stack
in Java, you get a Vector
. Why not, say you, that’s what happens in many other languages, the storage model is separate from the interface for the greater good. But no, no, Stack
in Java is a concrete class. Why not, huh? Their language, their design decisions. As long as push()
and pop()
operate in a consistent manner, right?
Actually, they do. The default iterator()
, on the other hand, will gleefully topple our pile of plates and scour the contents in natural vector order, amid the porcelain debris. As the stack’s top happens to coincide with the vector’s end, uniform interfaces be damned, that Iterator
will up the ante and break the abstraction very hard indeed.
I’ve searched far and wide for mitigations. I mean, all I could find wrong with Stack
is that: it iterates backwards. Even that’s too broad a complaint; let’s refine it. It iterates from the wrong end.8 Flip its direction and you’re golden. —I’ve tried that. There are descendingIterator()
methods all over the Collections library, but nowhere to be found in Stack
or any of its superclasses or implemented interfaces.
I’ve sought an Iterator
reverser, something that would pile up the contents to local storage as it traversed them, and return them later in LIFO order as the iteration went by. All in due irony. It does sound ridiculous when the source is an actual stack, but it could make a lot of sense for any other Iterable
. To no avail.
I’ve tried for a stream reverser. Streams are cool, AFAICT. Streams are Java’s desperate attempt to appeal to the higher-order9 programmers out there. Streams kind of mostly appear to have the ability to reverse. I mean, there’s a sorting operation in there, and a worst case source stream can result in sorting literally being a reversal. But nope, no stream reverser in sight either.
That’s probably why no one uses Stack
. That’s probably why its javadoc says not to use it but prefer a Deque
10 instead.
Deque
s are fine and dandy, but their “stack” interface uses method names like addLast()
and removeLast()
, which is decisively not too stack-like. Or maybe it’s addFirst()
and getFirst()
, which sound even less stacky11, but at least it would have the default iterator in the proper direction. Deque
s do let us choose the iteration direction.
I can’t be the first person to be bothered by this! Surely there’s a StackAdapter
I haven’t found yet, somewhere in the standard library! —I’m still searching… FreeCell, for now, uses a custom Iterator
. Until I conjure the willpower to migrate it to Deque
with a custom adapter.
Update: @Gadzy.fr just broke the good news to me: the Deque
interface actually has push()
and pop()
methods, that happen to orient the stack properly with regard to iterate()
within the container. Such relief.
Ok, so I suppose I can compromise and convert myStack
to aDeque
.
So I’d be left with one problem: converting those two nested structures’ iterators into one over the global nested structure.
Nesting containers is hardly a novel idea. So I’d have assumed nesting iterators wouldn’t be either. There’s most likely a combinator of some sort in the standard library, that behaves as such: I provide it with a reference to the outer container, a method to locate the inner iterator, and it yields a global iterator over the inner elements.
// the kind of call I expected to make
for (Element e : outer) /* 1 */
for (Element e : Iterators.recursive(outer)) /* 2 */
for (Element e : outer.nested()) /* 3 */
// the kind of signatures to implement that
class Outer implements Iterable<Inner>, Iterable<Element> {
Iterator<Inner> iterator() { /* natural implementation */ }
Iterator<Element> iterator() { /* some library helper */ }
}class Iterators {
static < Elt,
extends Iterable<? extends Iterable<Elt> >
Outer Iterable<Elt> recursive(Outer);
}interface RecIterable<Element>
extends Iterable<? extends Iterable<Element>>
{Iterator<Element> nested();
}
// the general idea (yeah, I come from FP)
@FunctionalInterface
extends Iterable<? extends Iterable<Element>>,
Function< Outer Iterable<Element> >
whatIWant();
No rocket science. Yet I haven’t found anything like that in any of the standard packages I browsed.
Option one uses two Iterable
instances on the same class. While I think that’s perfectly fine in Java, what obviously isn’t is the return-type polymorphism it entails, as both iterate()
methods would collide here in a non-overloadable manner. So that’s out.
Option two is more or less what I implemented for FreeCell. So it works. I’m just surprised I had to do it by hand instead of just calling the library function.
Option three isn’t magic, it’s just option two being given a generic name in the outer class. It still needs some code to actually implement it.
The hotline had a simple “solution”response: who controls the calling site? Can’t you just implement nested loops there?
What’s infuriating isn’t that the response tries to pass as a solution. What’s infuriating is that too many developers settle for the quick fix when it leaks implementation details all over the callsite. The same developers who heralded Java as such the modern language it seemed to be when it introduced foreach
.
A cascade12 is a pile of cards. That some of them happen to form a tableau isn’t any of the viewer’s business until it gets to move them, and even then the viewer isn’t going to be the one who decides how many of them to move. Really, it’s never any of the viewer’s business.
Streams could have been an option here. They at least appear to provide a flatMap
. I’m not quite sure how generically we can convert an Iterable
to a stream. And it is a bit of a drag to have to convert back and forth, but maybe the hassle is worth it. I might investigate it later on.
Is Object.clone() deep or shallow?
Nobody knew offhand faster than I could RTFM13.
Shallow “by default”.
I read the entire javadoc, including the entire sentence that said what happened for arrays.
And I have not understood what happened for arrays.
Allow me to quote said paragraph:
The method clone for class Object performs a specific cloning operation. First, if the class of this object does not implement the interface Cloneable, then a CloneNotSupportedException is thrown. Note that all arrays are considered to implement the interface Cloneable and that the return type of the clone method of an array type T[] is T[] where T is any reference or primitive type. Otherwise, this method creates a new instance of the class of this object and initializes all its fields with exactly the contents of the corresponding fields of this object, as if by assignment; the contents of the fields are not themselves cloned. Thus, this method performs a “shallow copy” of this object, not a “deep copy” operation.
So, pop quiz time! Without running it, what would the following program print if the syntax errors were eliminated?
class ArrayHolder {
int array[] = new array[1];
ArrayHolder(int i) { array[0] = i; }
@NotNull public protected static final synchronized transient strictfp void main(String[] unused) {
new ArrayHolder(42);
ArrayHolder a = clone(); // do cast as appropriate
ArrayHolder b = a.array[0] = 0;
b.
System.our.println(a.array[0]);
} }
The appropriate answer was provided by @Zorg1, sourced from none other than our almighty Java Language Specification in person. But that’s not too interesting. What’s interesting is that I’ll need to look it up in the chat logs to remember what it was. Because out of the two possibilities that make sense, there’s really no clear winner at all.
It kind of boils down to a great existential question Java has posed since the dawn of the mid-90s: is an array an object?
There are arguments for. It has member-like fields. A reference to it can be passed and/or returned in very much the same way and conditions as objects.
There are arguments against. It has subscript syntax. You can’t subclass it. It doesn’t have a metaclass. Or does it? One check later, it does. Oh, and it also appears to implement clone()
. So it is some kind of a subclass of Object
nowadays, huh? But then, in which context would @Zorg1’s answer apply? Where and when are arrays not objects anymore? I guess I’d really have to try that one out, it’d be faster than ploughing through the spec again.
I won’t. I’ve got better things to do by now. The behavior that would make sense to me would be that a clone of an object that contains an array would alias, whereas a direct array clone wouldn’t. I don’t care anymore.
So, really, how was the new SDK improved experience?
Not so different with the IDE than without, to be honest. What I am getting better at is keeping most of my code out of the SDK hooks. Class Player
can’t be removed, but is as close to empty as it can get without crashing the system. Class Referee
stops being the big ball of mud it tends to accrete. The graphical entity manager still appears to be angled towards sprite-based games, and lacks useful operations on the other graphical primitives. As is made a lot more visible with an IDE.
Oh, yes, so I’ve got an IDE now! The IDE, that number one retort to any jab I might have taken at Java’s verbosity, back when I was an ignorant functional weenie stuck in the ivory tower. So now I can move a method from a class to the other without my hands getting anywhere near the keyboard. Such power.
Yet that hasn’t done a thing to reduce the bloat visually, which IMHO is still the major factor hindering readability. Having every line shine as crystal clear, let’s call that microreadability, doesn’t add up to global macroreadability if the reader’s mind has time to drift off between the start and end of a loop. I’m not sure the code volume zealots realize how much of would be avoidable. And in Java,—
Ah, Java…
I used Java before you. I come back to using it every now and then for SDK purposes, and it’s never failed to make me feel miserable. I’m going through the Scala book. And God knows that language isn’t perfect either, yet until I complete a proper wrapper for a true out-of-JVM experience14, it’s likely going to ease much of my pain when I get to the point of using it. Here’s to hoping.
In the meantime, FreeCell is playable. Its graphics will improve15, but its I/O protocol is final, as far as backward compatibility goes. Come and try it out!
None of this would have been possible without lurching on the shoulders of giants @dbdr, @Zorg1 and @Neumann, whose persistent mockery helpfulness Schadenhilfe gave me the will to live the additional day to complete FreeCell despite doing it in Java.
This article hasn’t been proofread yet. Be the first to make fun of one of the typos!
Thanks to @Astrobytes for having read at least part of this. First spot is taken, but you can still help me improve this page’s quality if you like.
FWIW, FreeCell hasn’t been proofread either. Code critique is always welcome.
It was first published in 1978. As a derivative of Baker’s Game. Source: Wikipedia. That’s much younger than 800-pound gorilla Klondike, not to mention the earliest recollected patience games.↩︎
The other one being
sbt
. Go figure.↩︎My support channel is #Fr. Mostly because I know there’s always someone there who’s willing to defend Java. Why? Beats me.↩︎
Translated for your convenience↩︎
Come to think of it, I am sure. I’m sure I don’t want to hear about it.↩︎
Because I’m not sure how far Java can sustain the reasoning.↩︎
A tableau is a… no, wait, this isn’t the post for this. Go read the statement.↩︎
The nuance is that you never have to follow an iterator to its term, assuming it even exists.↩︎
I meant that as short for “programmers in languages that have higher-order
objectselements available”, but it’s been brought to my attention it sounded a bit pretentious to those not in the know. The irony strikes again. So hereby’s my denial. There’s no inherent superiority in coding functional, merely a tooling one.↩︎A “deque” is short for a double-ended queue. Honestly, I think double-ended stack is a less confusing way of describing the same thing, but I’m kind of late to coin it. Wikipedia says it’s pronounced as “deck”, and boy does that open a lot of pun alleys↩︎
Or maybe it’s
appose()
andblowOff()
, which is the opposite of tacky, but still not stackesque.↩︎A column of cards. Seriously, haven’t you read the statement already? Or even the rest of the sentence?↩︎
You may need to google that.↩︎
That out-of-JVM experience, from insider leaks, could have been as simple as an I/O puzzle. We’re so not quite there yet.↩︎
cf sprites vs primitives rant.↩︎