开发者

What is a good showcase for Clojure?

I'd li开发者_StackOverflow社区ke to run a session about Clojure. Could you recommend a problem which can be elegantly solved using functional programming with Clojure? Can you point to resources which cover this topic?


A lot of arguments for using Clojure seem to be related to its handling of concurrency, but I will not touch that issue here.

I will list some problems that I am forced to deal week in, week out with Java and how I have or would solve them in Clojure.

Immutability

In Java achieving immutability is very very hard. Besides following strict coding practices you will have to choose your frameworks and libraries very carefully. Also as a side-effect you will either write a lot of code to make a clean and usable API, or just force the client to deal with it.

final Person person = personDao.getById(id);
// I would like to "change" the person's email, but no setters... :(

In Clojure you model your data based on immutable data structures so all of your objects are immutable by default and due to this Clojure offers powerful functions which operate on these structures.

(let [person            (get-by-id person-dao id)
      person-with-email (assoc person :email email)]
  ; Use person-with-email...

Conversions

In Java you have a domain class Person with fields id, name, email, socialSecurityNumber and others. You are creating a web service for retrieving the names and emails of all the persons in your database. You do not want to expose your domain so you create a class PersonDto containing name and email. That was easy, so now you need a function to map the data from Person to PersonDto. Probably something like this:

public class PersonPopulator {
    public PersonDto toPersonDto(Person person) {
        return new PersonDto(person.getName(), person.getEmail());
    }

    public List<PersonDto> toPersonDtos(List<Person> persons) {
        List<PersonDto> personDtos = new ArrayList<PersonDto>();
        for (Person person : persons) {
            personDtos.add(toPersonDto(person));
        }
        return personDtos;
    }
}

Okay well that wasn't so bad, but what if you want to put more data in the DTO? Well the constructor code in toPersonDto will grow a bit, no worries. What if there are two different use cases, one as above and another where we want to send just the emails? Well we could leave the name null (bad idea) or create a new DTO, perhaps PersonWithEmailDto. So we would create a new class, a few new methods for populating the data... you probably see where this is going?

Clojure, a dynamically typed language with immutable data structures, allows me to do this:

(defn person-with-fields [person & fields]
  (reduce #(assoc %1 %2 (get person %2)) {} fields))

(person-with-fields {:id 1 
                     :name "John Doe"
                     :email "john.doe@gmail.com"
                     :ssn "1234567890"} :name :email)
; -> {:email "john.doe@gmail.com", :name "John Doe"}

And to manipulate a list of persons:

(map #(person-with-fields % :name :email) persons)

Also adding ad hoc data to a person would be easy:

(assoc person :tweets tweets)

And this would break nothing. In Java if your objects are immutable they probably don't have setters, so you would have to write a lot of boilerplate just to modify one field (new Person(oldPerson.getName(), oldPerson.getEmail(), tweets)), or create a totally new class. Mutable objects offer a nice API (oldPerson.setTweets(tweets)), but are difficult to test and understand.

Testing

A lot of Java code is based on some state even when there is no need for it. This means you can either mock this state, which usually means additional boilerplate and gets harder if you have not created your code with testability in mind. On the other hand tests without mocks are usually slow and depend on database access or time or something else that will surely fail on you from time to time.

While coding Clojure I have noticed that I do not actually need state that much. Pretty much the only situations are when I am retrieving something from the "outside", be it a database or some web service.

Summary

My code is a pipe, from one end I get some data, this data is then changed in the pipe via filtering, transforming or joining it with some other data until it reaches the end of the pipe. Inside the pipe there is no need to really change the data, but a lot of cases where powerful functions and immutable data structures are useful and this is why Clojure works wonders with code like this.


The classic concurrency problem "The Sleeping Barber" might be good.

Here is a couple of examples

http://www.bestinclass.dk/index.clj/2009/09/scala-vs-clojure-round-2-concurrency.html

https://github.com/bitsai/clojure-actors/blob/master/sleeping_barber.clj


A couple of months ago I run into the same problem and we decided to solve 'the Mona Lisa' problem in Clojure. The result was this presentation. Basically we showed that Clojure is extremely cool for solving problems for which 'code as data' gives an elegant solution. For example in genetic algorithms.

0

上一篇:

下一篇:

精彩评论

暂无评论...
验证码 换一张
取 消

最新问答

问答排行榜