No more loops with LambdaJ 1.8

Discussions

News: No more loops with LambdaJ 1.8

  1. No more loops with LambdaJ 1.8 (21 messages)

    How many times have you read or written the same two or three lines of code that frequently seem to go together, and even though they operate on different objects, feel like the same thing? And how often these repetitions involve some sort of collections iteration or more generically manipulation? These repetitions in the code is something that developers eventually learn to filter out and ignore when reading code, once they figure out where the interesting parts are placed. But even if the developers get used to it, it slows them down. Code like that is clearly written for computers to execute, not for developers to read. lambdaj is a library that makes easier to address this issue by allowing to manipulate collections in a pseudo-functional and statically typed way. In our experience to iterate over collection, especially in nested loops, is often error prone and makes the code less readable. The purpose of this library is to alleviate these problems employing some functional programming techniques but without losing the static typing of java. We impose this last constraint to make refactoring easier and safer and allow the compiler to do its job. Access collections without explicit loops The main purpose of lambdaj is to partially eliminate the burden to write (often nested and poorly readable) loops while iterating over collections. In particular it allows to iterate collections in order to: - filter its items on a given condition - convert each item with a given rule - extract a given property from each item - sort the items on the values of one of their property - group or index the items on the value of one or more properties - invoke a method on each item - sum (or more generally aggregate) the items or the values of one of their property - concatenate the string representation of the items or of the values of one of their property without to write a single explicit loop. How does lambdaj work? There are 2 ideas at the base of lambdaj. The first one is to treat a collection of objects as it was a single object by allowing to propagate a single method invocation to all the objects in the collection as in the following example: List personInFamily = asList(new Person("Domenico"), new Person("Mario"), new Person("Irma")); forEach(personInFamily).setLastName("Fusco"); In this example all the persons in the list belongs to the same family so they all have the same last name. The forEach method actually returns a proxy object that implements both the Iterable interface and all the methods in each object in the given list. That allows to invoke a method of the Person object on the object returned by the forEach method as it was an instance of the Person class. When you do that, under the hood lambdaj propagates your invocation to all the objects in the collection. The second idea on which lambdaj is built on is the possibility to have a pointer to a java method in a statically typed way by using the lambdaj's on construct. That allows to easily and safely define on which argument a given lambdaj feature has to be applied. For example in the following statement we used the on construct in order to say the argument (their respective ages) on which a list of persons has to be sorted: List sortedByAgePersons = sort(persons, on(Person.class).getAge()); lambdaj features As stated lambdaj is designed to easily manipulate collections. Its features are intended to filter, convert, index and aggregate the items of a collection without explicitly iterate on it. Moreover the lambdaj API are designed to be easily concatenated in order to jointly use two or more features in a single statement. To investigate these features in more details and possibly to download and start using lambdaj, check it out at: http://code.google.com/p/lambdaj/

    Threaded Messages (21)

  2. Syntax suggestion[ Go to top ]

    I think the syntax could be improved a bit. Instead of: filter(having(on(Person.class).getAge(), greaterThan(30)), meAndMyFriends); I would write: Person p = new Person(); from(persons, p).where(p.getAge()).greaterThan(30).list(); That would be less nesting. Or (if p.age is public): from(persons, p).where(p.age).greaterThan(30).list(); See also http://www.h2database.com/html/jaqu.html
  3. Re: Syntax suggestion[ Go to top ]

    Good to see so many projects that would benefit immensily from Java having closures, implementing abstract iteration mechanisms. In spite this was not the focus of our project (as it also handles fluent collections, better reflection, better interface on collections and reusing functions from all over, including Hamcrest and Google Collections), we crafted a very similar library called fluent java. One of the complex examples, as it can be seen here ended up looking quite similar to those above: Set result = books .findAll(hasProperty("year", greaterThan(1999))) .map("getAuthors") .flatten() .select(hasProperty("firstName", startsWith("M"))) .toSet();
  4. Re: Syntax suggestion[ Go to top ]

    Person p = new Person();
    from(persons, p).where(p.getAge()).greaterThan(30).list();
    That would require instantiating the object with default constructor which may not be possible, appropriate, or cheap. Excellent projects that mix FP with strong-typing!
  5. Why do we need this?[ Go to top ]

    How is this different from commons collections which appears to do the same things?
  6. How is this different from commons collections which appears to do the same things?
    Well, try to do the same thing with common collections and look at how many LOCs you need...
  7. The similarity to Commons Collections is entirely superficial. Commons Collections doesn't use proxies, doesn't support generics, etc.
  8. Re: FP in Java[ Go to top ]

    There is only one problem with this approach. Functional programming has no appeal to the masses. It will always be something for CS nerds.
  9. interesting[ Go to top ]

    No need to think functional to use this. Think about the common case when you are binding something to a Javabean property. Traditionally you go something.bind(person, "fullName"). This would bring some typesafety and refactoring support to those statements. And if you rename your getters and setters to: public String fullName() {..} public void fullName(String name) {..} the binding stuff starts to become even more natural.
  10. Re: FP in Java[ Go to top ]

    Luckily, this is not functional but declarative. Spreadsheets and SQL are functional / declarative, and both seem to be relatively popular :-)
  11. This looks like an interesting approach, but I have a couple of questions about it... If "forEach()" returns a proxy which implements both Person and Iterable, that returned object should be of a proxy class created on runtime (a subclass of Person, thus)... so how is it possible that the compiler knows it in compile time and allows you call both the methods of Person and Iterable on it? ...maybe your description is not exactly accurate and that returned object, seen from the point of view of the "forEach()" caller, *is just a proxy subclassing Person* (not implementing Iterable) that repeats all the operations performed on it on each of the elements of the underlying collection. But if that is the case, which I am pretty sure it is, I see two problems in "Person p = forEach(myPersonList)": 1. After that line, "p" is in fact a collection, but I will not be able to treat it like that anymore as I just can't see that. 2. After that line, "p" looks like a Person object, but in fact it is a collection (underlying), and as I cannot tell whether it really is a collection or not because being a runtime proxy, even a "p instanceof Person" check will return "true", I can easily confuse it and treat it like a single object, when it isn't... How could I address this problems? Regards, Daniel.
  12. If "forEach()" returns a proxy which implements both Person and Iterable, that returned object should be of a proxy class created on runtime (a subclass of Person, thus)... so how is it possible that the compiler knows it in compile time and allows you call both the methods of Person and Iterable on it? ...maybe your description is not exactly accurate and that returned object, seen from the point of view of the "forEach()" caller, *is just a proxy subclassing Person* (not implementing Iterable) that repeats all the operations performed on it on each of the elements of the underlying collection.
    Yes, your description of what actually happens is more correct than mine. At compile time the returned object is just a Person. But when you invoke it the proxy makes it behaves internally as an Iterable.
    "Person p = forEach(myPersonList)":

    1. After that line, "p" is in fact a collection, but I will not be able to treat it like that anymore as I just can't see that
    Indeed you don't have to treat p as a collection at all
    2. After that line, "p" looks like a Person object, but in fact it is a collection (underlying), and as I cannot tell whether it really is a collection or not because being a runtime proxy, even a "p instanceof Person" check will return "true", I can easily confuse it and treat it like a single object, when it isn't...
    p is a Person and you have to think about it as a Person. What lambdaj does for you is to propagate your invocations to all the Persons that are actually inside p. Of course that makes sense mainly if the invoked methods return void. If you're afraid you can get confused by the meaning of p just give it a more clearName like personCollection :) This approach has been empolyed in other lambdaj features. For example the sumFrom() method creates a proxy that allows you to have a proxy that aggregates (sums) the values of all the objects in a given collection as it follows Person personTotalizer = sumFrom(persons); Now personTotalizer is a Person that returns the totals of the requested methods. It means that: int totalAge = personTotalizer.getAge(); return the sum of the ages of the persons in the collection while: int totalAge = personTotalizer.getIncome(); returns the sum of their incomes. I hope this helps. Mario
  13. int totalAge = personTotalizer.getIncome();
    Sorry, I just made a too aggressive copy & paste in my former post :) Of course what I meant to write was: double totalIncome = personTotalizer.getIncome(); Anyway I hope it was clear the same. Regards Mario
  14. This approach has been empolyed in other lambdaj features. For example the sumFrom() method creates a proxy that allows you to have a proxy that aggregates (sums) the values of all the objects in a given collection as it follows Person personTotalizer = sumFrom(persons); Now personTotalizer is a Person that returns the totals of the requested methods. It means that:
    And that is the dealbreaker for me, because it totally changes the semantics of what a class is. Just imagine you return that Person instance from some deeply nested methods and the users of that API does not know about those runtime-proxies. I dont think this is usable for a huge codebase and I think it further demonstrates why lambas at the language level are necessary to do this kind of development. It is not just a question of "syntactical sugar", but of software quaility and development costs. How many bugs are introduced in software projects only today because every startes has to learn how to e.g. close a JDBC connection, iterate over a map and so on. I think we can produce better results when we have constructs that allow writers of libraries to abstract away stuff like this. why do MyResource r1 = null; try { r1 = allocateMyResource(); //do work } finally { try { if (r1 != null) r1.close(); catch(MostUselessAndHatedException ex){ ex.printStackTrace(); } } } when you could have withResource( createResource() ){ MyResource r -> // do something with it } This way the handling of the resource can be done correctly ONCE by the guy that writes the library and everybody else can have it the correct way without having to learn HOW to do it, but simple do WHAT you want to do.
  15. And that is the dealbreaker for me, because it totally changes the semantics of what a class is. Just imagine you return that Person instance from some deeply nested methods and the users of that API does not know about those runtime-proxies.

    I dont think this is usable for a huge codebase and I think it further demonstrates why lambas at the language level are necessary to do this kind of development.
    I think that if you "return that Person instance from some deeply nested methods" you're just using it in the wrong way. lambdaj is a tool, exactly in the same way as an hammer is a tool: you can hit a nail (right way to use it) or a person's head (wrong way). My team and I are using lambdaj since more than a year on 2 very huge projects and we never experienced a problem similar to the one you described.
    It is not just a question of "syntactical sugar", but of software quaility and development costs. How many bugs are introduced in software projects only today because every startes has to learn how to e.g. close a JDBC connection, iterate over a map and so on. I think we can produce better results when we have constructs that allow writers of libraries to abstract away stuff like this.
    I don't want to discuss if closures could be good in java or not. It is just an opinion. The only fact here is that they are not present in java at the moment and it seems they won't be in for a while still. Lambdaj just tries to partially fill this lack in a very limitated application field.
  16. I think that if you "return that Person instance from some deeply nested methods" you're just using it in the wrong way. lambdaj is a tool, exactly in the same way as an hammer is a tool: you can hit a nail (right way to use it) or a person's head (wrong way). My team and I are using lambdaj since more than a year on 2 very huge projects and we never experienced a problem similar to the one you described.
    I guess if you write your app in a way that does not expose those proxies, then you are safe, but that is still a "convention" that cannot be enforced automatically, I guess.
    I don't want to discuss if closures could be good in java or not. It is just an opinion. The only fact here is that they are not present in java at the moment and it seems they won't be in for a while still. Lambdaj just tries to partially fill this lack in a very limitated application field.
    I think it is a great idea, I dont want to take anything away from your efforts with this project, but I think that it should be possible to achieve the benefits of your library without being forced to use dynamic proxies and that is not meant as a critique against your library ;)
  17. I guess if you write your app in a way that does not expose those proxies, then you are safe, but that is still a "convention" that cannot be enforced automatically, I guess.
    It depends what you mean when you write "you write your app in a way that does not expose those proxies". Let me make a very practical example: suppose you have a table that has as model a list of beans. Each row of the table is the view of a bean and each cell the value of a numeric property of the bean related to the given row. In this very common scenario to have a row of totals for that table is straightforward by using the sumFrom() method of lambdaj. It is enough to bind the bean returned by that method to the row of totals exactly in the same way as the other beans are bind to the other row of the table. I hope I have been clear :)
    I think it is a great idea
    Do you think I could have wrote a library like this if I didn't love closures or without having closures in mind? ;) I was just saying (as suggested by Joshua Bloch in the presentation I linked) that, despite closures are wonderful, it couldn't be a wonderful idea to put them in a second stage in a language that evidently has been thought and implemented without them.
    I don't want to take anything away from your efforts with this project, but I think that it should be possible to achieve the benefits of your library without being forced to use dynamic proxies and that is not meant as a critique against your library ;)
    I love constructive critiques as much as I love closures. Of course a constructive critique should suggest a feasible alternative or change, so at the moment I can't consider "why don't we use closure in java" in that way.
  18. Or you can use Groovy...[ Go to top ]

    I think that looks nice to Java programmers. List personInFamily = asList(new Person("Domenico"), new Person("Mario"), new Person("Irma")); forEach(personInFamily).setLastName("Fusco"); Let me note that with Groovy you could do similar stuff. You could use the "each" method with a Closure as argument that describes, what to do during the iteration. List personInFamily = [new Person("Domenico"), new Person("Mario"), new Person("Irma")] personInFamily.each { p -> p.setLastName("Fusco") } I think that's just as readable as the lambaJ example. There are much more groovy extensions to the Java Collection API. Check it out! Cheers, Armin
  19. ... or Scala[ Go to top ]

    One of the ideas behind lambdaj is exactly to provide some facilities (that are available in a more natural way in more modern languages) in Java for people who don't want to (or more likely just can't in some specific projects, as it happens to me) move to a different language. Anyway if I was allowed to choose another language to develop the projects on which I'm currently involved, I suppose I will be happier to move to Scala. Bye Mario
  20. This approach is nice, however it would be much cleaner if we had support for lambdas directly in the language. This way we could use the higher level abstraction benefits in all places, not just on Iterables. Take a look at Code you could write, if Sun had not built JavaFX, but focused on bringing the Java language and the JDK in general forward. final double highestGpa = students .filter({ Student s => s.graduationYear==THIS_YEAR }) .map({ Student s => s.getGpa() }) .max();
  21. Not everybody agree that to have closure in Java could be a good idea. Here you can find the best explanation of how and why closure in java could be painful (and that is the reason why that feature has been removed from Java 7): http://www.javac.info/bloch-closures-controversy.ppt
  22. Oh, you mean the "carefully chosen" examples that demonstrate deficiencies that arent really related to Closures but are cause by defered execution in general. E.g. the sample with the addition of those integers ("45 or 100?") is not a result of closures, but of deferred execution. We already have that by anonymous classes, but they are verbose and totally unuseful, because they dont allow for completion transparency. Neal Gafter explains the "deferred execution topic" in more details here: http://www.infoq.com/presentations/neal-gafter-evolving-java