Quantcast
Channel: Hacker News 50
Viewing all articles
Browse latest Browse all 9433

4 Things Java Programmers Can Learn from Clojure (without learning Clojure) | LispCast

$
0
0

Comments:"4 Things Java Programmers Can Learn from Clojure (without learning Clojure) | LispCast"

URL:http://www.lispcast.com/java-learn-from-clojure?utm_source=dlvr.it&utm_medium=twitter


I was trained in Java at University. The OOP matrix was firmly implanted in my thinking. I wanted to share some things that I have learned from Clojure that were certainly possible in Java but never became fundamental to my programming practice.

Clojure certainly has learned a lot from Java. It might be cool if the learning went both ways. These are universal principles. In fact, these principles are actually well known in the OOP world. You probably already know them, so learning Clojure is not required (but it is recommended!).

1. Use immutable values

One of Clojure's claim to fame is its immutable data structures. But immutable values were appreciated even in the very early days of Java. String is immutable and was a bit controversial when Java came out. Back then, C and C++ strings were simply arrays which could be changed. Immutable Strings were seen as inefficient.

However, looking back, immutable Strings seem to have been the right choice. Many of the mutable Java classes are now seen as mistakes. Take, for example, java.util.Date. What does it mean to change the month of a date?

Let's go a little further. Let's imagine that I'm an object. You ask me when my birthday is. I hand you a piece of paper with July 18, 1981. You take that home, store it somewhere, and even let other people access that piece of paper.

One of those people says "cool, a date!" And changes it to his birthday, April 2, 1976 using setTime. Now the next person who asks for my birthday actually gets that guy's birthday. What a disaster! Why did I give away that magic paper that changes my birthday?

By making values mutable, this magical-changing-at-a-distance is always a possibility. One way to look at the reason it is actually wrong to use mutable values is that it breaks the information hiding principle. My birthdate is part of the state of my object. By giving direct access to the month, day, and year, I'm actually letting any class have direct access to my internal state.

The answer, of course, is to not have any setters on an object. After construction, the object can't change. That way, my internal state remains encapsulated.

This applies to collections as well. Have you ever read the docs for Iterator. Can you tell what happens when the underlying list changes? Neither can I. An immutable list would not have such a complicated interface.

Solution: Don't write setter methods. For collections, you have a couple of options. One easy thing to do is use the Google GuavaImmutable... classes. If using Guava is not an option, whenever you are returning a collection, make a copy, wrap it in a java.util.Collections.unmodifiable...(), and throw away the reference to the copy.

public static Map immutableMap(Map m) {
 return Collections.unmodifiableMap(new HashMap(m));
}

To learn more about immutable values, I suggest watching The Value of Values, a talk by Rich Hickey, the creator of Clojure.

2. Do no work in the constructor

Imagine this situation. Your Person class has a constructor that takes a bunch of information (first name, last name, address, etc.) and stores it in the object's state. Someone on your team needs to store that data to a file, so stores it as JSON. For convenience in creating a Person, you add a constructor that takes an InputStream and parses it as JSON, then sets up the state. Just because, you also add one that takes a File, reads in the file, then parses it. And then one that reads in a web request given a URL. Great! You've got a very convenient class.

But wait! What is the responsibility of the Person class? Originally, it was "represent personal information about a person". Now it is also responsible for:

  • Parsing JSON
  • Making web requests
  • Reading files
  • Handling errors

What's more is that the class is now harder to test. How can you test the File constructor? First, you write a temporary file to the file system. Not too bad. How do you test the web request? Set up a web server, configure it to serve the file, then call the constructor.

The problem is that Person violates the single responsibility principle. Person is about keeping bits of information together, not permanent storage or serialization. It should be a data object, no more.

To learn more about this idea (and more!), I suggest you watch OO Design for Testability by Miško Hevery, the creator of AngularJS.

3. Program to small interfaces

One thing that Clojure has done very well is to define a set of very powerful, small interfaces which abstract a pattern of access. The interface allows many different types to participate in an "ecosystem". Any function which applies to the interface can act on any type that implements that interface. Any new type can take advantage of all of the existing functionality already built in.

Take for instance the Iterable interface. It generalizes anything that can be accessed sequentially (such as a list or set). If all a method needs to do is operate on something sequentially, it only needs to know that it implements this interface. That means it can operate on types that were not known to the programmer when the method was written.

This aspect follows from the dependency inversion principle which states that high-level logic should be written in terms of abstractions instead of the details of the lower-level logic. Interfaces capture this principle well. High-level logic should operate on interfaces which are implemented by the lower-level logic.

Solution: Think hard about the access patterns for classes and see if you can't abstract out small interfaces which pinpoint those access patterns. Then program to those interfaces. Remember, it takes two to use an interface: the implementor and the client. Make sure you use them from both sides as much as possible.

Nothing increases maintainability and the future cost of code more than good interfaces. To learn more about this, I suggest watching How To Design A Good API and Why It Matters. It's an older (but good) talk by Joshua Bloch.

4. Represent computation, not the world

When I was in college, the teacher taught us that you should use classes to model objects in the world. The quintessential modeling problem was students registering for courses.

A course can have many students and a student can be registered in many courses. A many-to-many relationship.

The obvious choice is to make a Student class and a Course class. Each has a list of the other. Inclusion in that list represents registration. Methods like register and listCourses let a Student register or list the courses he's registered in.

Professors would present this problem in order to discuss the tradeoffs of different design choices. None of the configurations of Student and Course were ideal. An astute data modeler would see the pattern of the many-to-many relationship and abstract that out. You can create a class called ManyToMany<X,Y> that manages the relationship. You can create a ManyToMany<CourseID, StudentID> and it solves your problem exactly.

The issue is that this directly contradicts the teacher's lesson. A relationship is not an object in the real world. At best it is an abstract concept.

What's more is that it solves the more general abstract problem as well. The ManyToMany class can be reused anywhere it is suited. Even better would be to make ManyToMany an interface with many possible implementations.

I think my professor was wrong. The Java standard library contains many classes that are purely computational. Why can't application programmers write them as well? Further, look at the GOF Design Patterns Book. Most (if not all) of the patterns are about abstracting computation, not "objects in the real world". Take, for instance, Chain-of-responsibility, which Wikipedia describes as "Avoid coupling the sender of a request to its receiver by giving more than one object a chance to handle the request."

Solution: look for repetitive patterns of code and build classes that take care of those patterns. Use those classes instead of repeating the pattern over and over in code.

You may be interested in my Introduction to Clojure Videos.


Viewing all articles
Browse latest Browse all 9433

Trending Articles