Hacker News new | past | comments | ask | show | jobs | submit login
A bad citizen in Javaland (2006) (darrenhobbs.com)
219 points by bencoder on Dec 3, 2015 | hide | past | favorite | 63 comments



I wonder how a functional version of this would go..

You need transportation, so you define an engine and a set of wheels and a chair, and a whole universe. You define a way to have the engine, wheels and a chair work together. You try to sit in the chair, but it's too small. You can't change the chair because it's all immutable, so you create a new chair along with a new universe and build a new car with the new chair.

You need to get to work, but you don't want to pollute your universe with state, so you don't know where work is. So you put all of your state in your relational stateful database and forget that this part exists in your universe. Of course theoretically you could model your knowledge as a function based off the state of the universe, but you don't have budget to do that. You write comments about quick hacks to get around purity constraints. The hacks are never removed and you end up passing stateful maps of database tables around. You make a big todo note about how you should implement a new method for handling this that you've read in a new academic paper, but never get the 500 hours you need to implement it.

EDIT: For those not getting it, the article is describing the difference between the theory behind OO interaction between self contained actors and how this really plays out in actual code. Instead of doing the theoretical part of communication between actors, we have code which does getCoworker().getWallet(). In the same way above, I've tried to show the difference between theoretical functional programming where we can create pure functions and how this actually plays out in real functional code.


You can't change the chair because it's all immutable, so you create a new chair along with a new universe and build a new car with the new chair.

No, if you read Okasaki, you knew you only have to reconstruct the part of the universe (up to its origin) that refers (directly or indirectly) to the seat ;).

In fact, you can safely have two universes with different seats, where most of the universes is shared.


Oh that makes for a funny part of the ending too then.

While going about your work, you suddenly disappear along with your universe and everything in it. Someone somewhere in the world drank a cup of coffee, which means that he now has an empty cup instead of a full cup. A new universe had to be created with the full cup replaced, and unfortunately your universe had to be thrown away. You continue life as a clone in a new universe with an empty coffee cup somewhere in Nebraska.

Why did this happen? Someone picked a copy-on-write map implementation because it had a slightly faster O(logN) delete behavior and unfortunately the re-use function of the map wasn't implemented yet because the author has gone on to do a series on the use of monads in baking the ultimate croissant.


"Someone somewhere in the world drank a cup of coffee, which means that he now has an empty cup instead of a full cup"

That cannot happen in an immutable world. To fill an empty cup, you cons a cup and its contents. To get an empty cup again, you take the car of the cons. The full cup will only get garbage collected if you do not reference it anymore.


> To fill an empty cup, you cons a cup and its contents.

That simply has the wrong type in mathematically civilized languages like Standard ML or Haskell.

> The full cup will only get garbage collected if you do not reference it anymore.

This is orthogonal to functional programming. You can avoid the need for a garbage collector with substructural types.


Hence, Many World Interpretation of quantum mechanics.


Sorting in O(n) time:

1. Apply a quantum permutation to your list.

2. Check if it's sorted.

3. If not, destroy the universe.


Related (amazing) paper by Scott Aaronson: "NP-complete Problems and Physical Reality" (http://arxiv.org/abs/quant-ph/0502072). He proposes "anthropic computing" as a way of solving NP-complete problems in polynomial time: guess an answer, then kill yourself if the answer is wrong.


It would be very eerie if suddenly all the guesses started being right.


I am worried that someone will actually try this sometime and it will turn out not to work, either because MWI is wrong or because the destruction of the universe was incomplete — for example, collapsing the false vacuum would merely change the state of the things in the universe, not destroy the universe per se.

Fortunately, we don't have any currently promising avenues for nucleating a true vacuum or anything like that, so this algorithm is purely theoretical.

For now.


That made me think of Greg Egan's Quarantine:

https://en.wikipedia.org/wiki/Quarantine_%28Greg_Egan_novel%...


Best-case runtime = 1. Worst-case = infinity.


I think the joke is that in all cases but the best-case, you won't be around to observe it, this becomes practical once you can create a universe for every permutation and destroy all but the universe which notifies you that it's the best-case.

In all good text books this is left as an exercise for the reader.


In a really good text book, this is left as an exercise that the reader won't do.


No, checking whether a list is sorted is O(n) ;).

Bogosort is O(n) in the best case and O(inf) in the worst case).


Optimize out the check, so it will be O(1) in some cases. ;)


The convergence of this method depends on the underlying distribution for your random process. It could be O(never).


As Scott Aaronson points out, the easy solution to this is that you add a slight quantum chance that the algorithm will yield "Reject". As long as that chance is non-zero, but greatly smaller than the probability of any particular successful permutation, you will survive in the universe that says "Reject".


This sounds like a joke but this is exactly the proof that sorting is in NP.


What is a quantum permutation?


A new chair isn't created - the legs, seat, and back of the old chair are re-used with new screws.

If you want to replace a leg, you still need to disassemble the chair and reassemble it with new screws.


I've never considered the many-worlds interpretation of quantum physics under the light of persistent data structures.. very interesting!


"If you want to make an apple pie from scratch, you must first create the universe." — Carl Sagan


I was about to dismiss this story as silly, but then it occurred to me that the reason this is so funny is not because Java is a bad language per se, but that it really didn't work well for programmers who don't understand Object Orientation. I've read more than my fair share of code that reads exactly like this story because of this lack of programmer understanding.

Consider:

You are born with no body parts.

    Person me = new Person();
Should anyone attempt to interact with you before your organs have finished arriving you will die.

    me.interact();
Throws a NullPointerException because the class designer improperly initialized the instance. The designer should have provided a constructor.

    public static Person() {
        this.organs = new Organs();
    }
One by one, your organs are pushed into you.

Instead, the class designer probably followed the JavaBean pattern and provided a setter method for Organs:

    public void setOrgans(Organs organs) {
        this.organs = organs;
    }
etc.

(For those that don't remember: The JavaBean idea arose because early versions of Java had no way of performing introspection, something that software tools really, really need. JavaBeans broke object encapsulation by design. Many programmers had to write beans to take advantage of their tools, and learned a very bad habit.)


> The JavaBean idea arose because early versions of Java had no way of performing introspection

Not quite. Java has had introspection since 1996. What JavaBeans brought is a consistent way of naming your methods so that classes can be introspected and made sense of in a way that allows composition and interaction.


I stand corrected. :)


In modern Java, one uses things like dependency injection (Guice, Spring, whatever) for building up the services, and I find most programmers now abhor setters for their POJOs. Want a new Person object with a different name? Make a new one, it'll be okay, except in the most extreme of circumstances. Ephemeral memory is cheap. When an object comes back from a constructor, it should (in my mind at least) be immediately usable. People also enjoy the builder pattern, I've found. When you call .build(), it will do the internal checks to say if you screwed up, so you're guaranteed to never have a Person with no Organs. This is, of course a run time check so less helpful than a compiler error. Depending on the use case, it makes perfect sense, though.

Also side note, how do I do the code blocks in HN comments like you did?


There's still a lot of getters everywhere though, which seemed to me to be the bulk of the analogy.

    this.wallet = colleague.getWallet();
Code blocks are done with 4 space indentation at the start


Which is not a fault of the Java language: trainwrecks (this->getThat()->getThose()-foreach(){ do stuff } is a general antipattern.

The correct way is IMO: colleague.getMoney() which can easily be implemented in Java, PHP etc.

I'm getting a bit tired of this constant pecking at Java (nothing personal). There are a number of questionable designs yes, there is a lot of ugly code out there, yes, but almost every alternative might have been worse:

Teaching everyone C? Good luck.

Switch to C#? Until recently that would prevent you from using Linux on your servers unless you were ready to put in technical effort and bet on MS not suing you or the upstream effort.

Perl?

PHP?

Catch my drift folks? Java filled a niche. It was almost as easy as PHP once you got started (although onboarding still is messy I think) and provided superior tooling.


Despite the name, I didn't see the article as taking a pop specifically at Java. It seemed more about bad OOP in general. As you say, you'll see the same patterns in every OO language.


The story is explicitly about a bad citizen in Javaland.


I think people just hate all the hoops you have to jump through to satisfy arbitrary constraints the language places upon you to conform to some pedantic definition of object orientation that only works well in textbook examples.

Not saying Java isn't a good choice for some things. Also, if functional programming languages were more popular, they would probably be subjected to the same ridicule for the same reasons.


By the same token, I think the superior tooling is part of the reason people who aren't primarily Java users find it incredibly painful. I've seen Eclipse experts in action -- most of the work seems to be picking the right thing from a drop-down. All kinds of things just appear where they're needed. Accustomed as I am to typing source code, I can't quite get my head around it.


>> Which is not a fault of the Java language: trainwrecks (this->getThat()->getThose()-foreach(){ do stuff } is a general antipattern.

While I agree with you, isn't that what the new streams in Java does? The map reduce complex?


  you only need two spaces, actually
https://news.ycombinator.com/formatdoc


Relying on dependency injection is even worse than setters. Will DI behave the same in tests as in real code? Are you sure?

The right thing is to use an actual constructor that takes constructor parameters. Guice and Spring can both handle this correctly (you just put the @Autowired on the constructor itself). The trouble is that in Java this requires ~twice as many lines as putting a magic annotation on your fields.


Especially in the multi-core era, this becomes more and more important. Thanks to the Java Memory Model specification, if you are using constructors (and yes, pretty much every DI framework by now has this feature), with just a bit of care[1] you can ensure that your object is never visible to any threads without it having been completely constructed.

[1] Make sure to not leak references to your object in your constructor. Most of the time that you would want to do this is starting a service or registering a handler, etc. The solution is to use a static creation method which first calls the constructor, and then registers/subscribes it, etc.


> (For those that don't remember: The JavaBean idea arose because early versions of Java had no way of performing introspection, something that software tools really, really need. JavaBeans broke object encapsulation by design. Many programmers had to write beans to take advantage of their tools, and learned a very bad habit.)

I have wondered what that was about. Thanks for clearing it up.

It is strange how the lack of a feature people really need (or want) can cause them to do weird things to work around that lack.


Yeah same, to me it seemed like poking fun at bad OOP practices more than anything, and the last paragraph is just casting garbage collection in a funny way.


Well, this made my day! I'd almost forgotten that blog was still running. Achievement unlocked :)


This is one of the best metaphors I've read in a long time!


I feel that this is a representation of the fundamental misunderstanding of how object orientation is really done. In my opinion, if designed this way, this application was not well thought out.

If I was writing this, I would aim for this:

You wake up one day and you call to the heavens asking for transportation, you yell out the location in front of you, and with no response from the skies a self driving car appears.

You walk to the car and ask it to take you somewhere. Suddenly a pair of hands reaches out from the car, pulls you in, and takes you to your location. While you arrive, you are pushed out.

When you get to your job, you need money for lunch. You hand your wallet to your friend and your friend puts money into it giving it back to you. You keep your friends phone number on record if you would like to ask for money again. If he is busy, you can leave a voice mail and eventually he will throw money at your face hoping you catch it.

When you finally get home, you go into your bed and sleep until your bed wakes you up the next day to start again.

It's silly, like the original post, but I prefer to give handles and let abstracted code do it's magic. In all of these cases, I do not have the caller assume anything. It does what it knows it can do.

Some things need a more fine tuned application of this, but the idea is you just never assume something is true, only use what you know. That's where a lot of people mess up in java.


note: It's called "A bad citizen...".


Stuff like this is fascinating to me. I'm by no means an expert in writing code, I'm very much a beginner, but the creative potential that is there has always drawn me in.

If one was going to learn OOP correctly in Java (or Python) from the beginning, where would be a good place to start? Are there any textbooks or good websites for this?


The SOLID principles are all you need to know. [0] Then realize through them that inheritance as generally taught is actually a bad idea and use interfaces instead. Then realize that interfaces are just a weaker version of type classes and reach enlightenment.

[0] https://en.wikipedia.org/wiki/SOLID_%28object-oriented_desig...


I had this thought recently that inheritance was a bad solution to languages not supporting containers natively. And or a lame attempt at code reuse. (A root class is an API/Service with no versioning)


Is this in reference to the type classes as seen in Haskell or something else? I want to reach enlightenment but I'm stuck in Java for the time being.


Yes, as in Haskell type classes. Or Rust traits. Basically, anything that acts like an interface (defines a contract) but allows external definition. So you're not stuck wrapping your Integer in a ShowableInteger just to have an Integer that implements the Show interface.


OOP allows you to avoid code like this:

function makesound() { if( thing is a Cat ) return "meow"; else if (thing is a mouse) return "eep": else if (thing is a sheep) return "baa"; }

Code like this in an OOP language would be replaced by creating classes, such that the structure of the program implicitly executes the if else switch above, without you having to write it.

class Cat { makesound() { return "meow"; } }

class Mouse { makesound() { return :eep"; } } . . .

Then if all those classes inherit from a common animal ancestor, the calling code would simply say: animal.makesound()

This becomes very convenient when the program behaviour relies on a combination of two or more types.

In that case the if else example would have to contain nested and possibly repeated ifs (or calls to functions) to handle the logic for all the variations.

Note that inheritance in this example isn't to reuse code, just to establish the relationship between subtypes.

If you become tempted to use the type system as a clumsy code reuse mechanism, you will encounter alligators for sure.

Neither type of code is good or bad, just convenient for different purposes or not.

OOP is especially nice for writing simulations. See: https://en.wikipedia.org/wiki/Simula


> Then if all those classes inherit from a common animal ancestor, the calling code would simply say: animal.makesound()

Which of course throws an exception in the case of Monk (inheriting from Animal -> Human), as it has taken a vow of silence.



I'd love to see an annotated version of this.


It's not quite the same sort of thing, but this reminds me of 'My Life as a Forth Interpreter' (1986): https://news.ycombinator.com/item?id=7410883


This is great! I can't help but feel there's also an opportunity in a sequel to allude to the all too common "invent arbitrary abstract base classes to keep my code DRY" anti-pattern.


For learning purposes for those reading this, what would you consider the alternative?

I personally prefer just stuffing commonly used stuff into utility classes containing only public static functions. Makes them really easy to test, too.


Thanks for asking! My answer, of course, depends on the particular scenario. I've had success with simple static methods, especially early in a project. I'd also consider some kind of composition (has-a vs is-a). The latter can be especially nice as static methods gain complexity in the form of more parameters/variations. This also shines if there's meaningful alternative implementations that can be swapped or if you're writing business logic and some legitimate business concepts start to congeal out of the code being re-used.


I can't understand java programmers telling me they don't like C.


A bad citizen in C counts to 32768 which corrupts the very fabric of the universe, replacing New York with half of Los Angeles and leaving 25 people repeating the same day indefinitely.


That sounds like a plot of a typical superhero comicbook. I guess we know what language Marvel's universes are written in.


Every time a character swears they speak in Perl.


But the universe is certainly written in Brainfuck.


Still better to correct this mistake than to think about the intricacies of the java abstractions.


This is true horror.




Consider applying for YC's Spring batch! Applications are open till Feb 11.

Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: