google / guava

Google core libraries for Java
Apache License 2.0
50.13k stars 10.89k forks source link

FluentIterable #11

Closed gissuebot closed 9 years ago

gissuebot commented 9 years ago

Original issue created by jonhnnyweslley on 2007-10-22 at 05:12 PM


Hi,

Last weekend I thought: "Why not to use functional programming with Java? More precisely using Google Collections!" ...

Minutes later... Voila!!! I can to write pieces of code as:

names.select(new Regexp(".*e$")).transform(new GetLength()).select(new OddNumber())

I wanted to move this code to a fluent interface. Now, I dont need the verbose syntax of the static methods, and, mainly, this code is more easy to read.

The attached jar file contains the full source code with:

About Fluente Interface: http://martinfowler.com/bliki/FluentInterface.html http://en.wikipedia.org/wiki/Fluent_interface

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2007-10-22 at 05:47 PM


Thanks!

Also see these blog posts (and my comments on them)

http://blogs.warwick.ac.uk/chrismay/entry/writing_functional_java http://stephan.reposita.org/archives/2007/10/17/creating-a-fluent-interface-for-google-collections/

In short, the "FP" features of the library could definitely be made a lot better, although we have to make a decision about whether it's central or tangential to what our library is trying to offer.


Owner: kevinb9n Labels: -Type-Defect, Type-Enhancement

gissuebot commented 9 years ago

Original comment posted by jonhnnyweslley on 2007-10-23 at 06:50 PM


Hi Kevin,

I read your comments. But I dont liked a little of the one in url: http://stephan.reposita.org/archives/2007/10/17/creating-a-fluent-interface-for-google-collections/ You wrote: "The other fix that I had in mind: make Function and Predicate into abstract classes which have the transform(), filter(), and other related methods right there on them."

I think that Function and Predicate must to continue as interfaces. However, you can to make abstract classes that implements each one them. Thus, the API continues flexible.

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2007-10-23 at 06:58 PM


Of course there must be interfaces.

In my experience it's very rare that people wouldn't extend the abstract class. But I have had a case where I wanted some predicates to be enums, and I wouldn't close off that possibility.

The interesting question is: if usage of the abstract class proves to be 10, 20, 50 times more popular than usage of the interface, does it merit a reversal of the usual naming practice? That is, instead of "class AbstractPredicate" and "interface Predicate", is there justification for "class Predicate" and (don't spew your coffee, please) "interface IPredicate"?

Heresy, I know, but I get paid to consider heresy :)

What I really fear is making the line

   Predicate<Set<? extends Number>> containsFive = new Predicate<Set<? extends Number>>() {

yet another eight characters longer, due to the need to instantiate AbstractPredicate instead.

gissuebot commented 9 years ago

Original comment posted by ymeymann on 2007-11-04 at 04:14 PM


Hi Kevin,

While using google-collections in my project, I created FunctionChain class that may solve this problem. You can look at the code here: http://docs.google.com/View?docID=d35w57w_0fc5tk2&revision=_latest

Usage example: Iterables.any(annotations,
self(Annotation.class).function(annotation_type).select(isInstanceOf(Foo.class)));

I tried to address 2 things: (a) keep Function as interface (b) get rid of NPE's by creating NullSafeFunctionChain, the idea is to let functions that declare @Nullable parameter in apply() to handle nulls, and skip invocations of the ones that don't declare @Nullable so that null just gets passed up the chain.

What do you think?

-- Yardena.

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-03-17 at 04:49 PM


We have been experimenting internally with "fluent" versions of common types.

The notion here is to follow the lead of the pair:

   java.util.Comparator (interface)    com.google.common.collect.Ordering (abstract class)

The latter implements the former, has a from() method taking the former, and is in all ways the same thing as the former except having more awesome functionality built-in.

This seems like a pattern that, while not totally 100% awesome, is worth following because we don't have any better idea how to do this.

So what we're playing with:

  Comparator --> Ordering   Iterable --> FluentIterable (??)   Predicate --> Matcher (??)   Function --> FluentFunction (??)

For example, FluentIterable looks like this:

abstract class FluentIterable<T> implements Iterable<T> {   FluentIterable<T> unmodifiable()   FluentIterable<T> filter(Predicate<? super T>)   <S> FluentIterable<S> transformWith(Function<? super T, S>)   FluentIterable<T> append(Iterable<? extends T>)   FluentIterable<List<T>> partition(int)   FluentIterable<T> skip(int)   FluentIterable<T> limit(int)   FluentIterable<T> cycle()

  int size()   boolean isEmpty()   boolean contains(@Nullable Object)   boolean elementsEqual(Iterable<?>)   T[] toArray(Class<T>)   String toString()

  ImmutableList<T> toList()

  T getOnlyElement()   T getLast()

  int frequency(@Nullable Object)

  T firstMatching(Predicate<? super T>)   boolean anyMatches(Predicate<? super T>)   boolean allMatch(Predicate<? super T>) }

Any input?

gissuebot commented 9 years ago

Original comment posted by jim.andreou on 2009-03-17 at 06:14 PM


Years ago I had experimented with returning "ExtendedIterable" (and other such types) from methods, which offered filtering and other utilities, but I came to dislike these and prefer the simplicity of google collections, in the form of not introducing new types for just syntactical "niceties", like calling "x.m(y)" instead of "m(x, y)" (modulo polymorphism, which you don't need, it's merely a syntactical twist). Would you feel comfortable with having FluentCollection, FluentSet, FluentMap, FluentMultimap etc? I doubt. Or for example: What if a method wants to return a Set (to denote element uniqueness) that is also "fluent"?

In the case of Ordering, I see comparator as a SPI and Ordering as its respective API. "You implement just this method(s), and you get for free all these". It looks good, I think static methods would be fine too. I understand you like the ability to hit "[dot]" and get the pop-up of your IDE, but hitting "[dot]" after typing "Ordering" would do the job as well. Also note that the Ordering methods are not also offered as statics, so they offer something genuine, while the types you describe would not.

So, I would hope you continue to prefer simplicity over slight syntactical gains, as you always have done in google collections.

I also want to draw some attention to the fact that this conversation wouldn't need to ever happen if the initial collection types were defined as abstract classes, and not interfaces. All convenience methods could be then defined in them. But then, existing classes with existing superclasses (other than Object) wouldn't be able to implement these collection types. This restriction doesn't exist for example in scala, where traits (interfaces that can have code) can be freely mixed in classes without affecting their class hierarchy, i.e. very easy reuse. So there is no reason not to define convenience methods directly in the collection types, and this is how is done. But since we talk about java, lets not try to fix the absense of certain language features by library design - this would encourage everyone to follow suit.

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-03-17 at 09:40 PM


You certainly are right that I'd hate to see this proliferate to Set and Map and all that.

If Ordering/Comparator is a little different from the rest (API/SPI), then Function and Predicate fall into this category as well. They're very much the same flavor of objects. (The point about how the functionality is not also offered with statics -- well, it was, we just undid all that is all.)

So it's really FluentIterable that is most controversial here. Well -- I love FluentIterable. I think it makes code a lot more readable and library features more discoverable. But I can't predict whether it will ever become part of this library or not. Your input has been useful, as will be the input of others who might wish to give it here.

gissuebot commented 9 years ago

Original comment posted by earwin on 2009-03-17 at 10:22 PM


  I second the vote for having interfaces that define all these new fluent methods. Whatever blasphmous name they might have, they are needed. Indeed, I'm currently missing an interface for the Ordering.

  So it goes like: Iterable -> i Whatever -> abs Whatever Comparator -> i Ordering -> abs Ordering i Function -> abs Function i Predicate -> abs Predicate

  Then, if you're introducing upgraded interface for Iterables/Comparators and Functions/Predicates with fluent methods, you're going to use them in APIs (and not only g-col ones, you're going to pass them around in your software). That way, even if you almost always use abstract classes that implement all that fluency as a base for your anonymous classes, the number of said anon classes would be on even, if not dwarfed by interface API usages.

  From that point I'm no longer sure which names should be shorter. What I'm not eager to see is AbcInterface or AbstractAbc. BaseAbc sounds better. IAbc even more. I'd really like to have +1letter naming for abstract classes, but can't choose a letter.

  What about naming upgraded Iterable a Sequence?

  Predicate is a cool name, it goes better with Function and it doesn't clash with java.util.regex.Matcher, which is much more probable to meet in the same context, than all non-g-col Predicates I could find.

Hopefully, I was able to convey my thoughts :)

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-03-17 at 11:00 PM


I still don't see the advantage of an interface for Ordering. Can you try again to explain it?

Whatever the "better" Iterable is, I'm convinced it must include "Iterable". Picking another term out of the air like "Sequence" is just going to mislead people; that may mean something else to them. An Iterable is exactly what this is, just with more stuff.

By this rationale the name Ordering was a mistake, and maybe it was, but it's one we already made and we'll live with it. And by this rationale, the name "Matcher" is wrong for the upgraded Predicate, and I think it is indeed wrong, for these reasons as well as conflict with all the dozens of other things called Matcher in the world. So I think we'll have to fix that, but to what?

gissuebot commented 9 years ago

Original comment posted by earwin on 2009-03-19 at 07:18 PM


Similar to your case - an ability to use Ordering enums. Other case is when you have your own working hierarchy of classes and want them to be FluentWhatevers. This case is shaky, it's more applicable to iterables/functions/predicates than to ordering and you can always have a method returning delegating FluentWhatever.

It's just a joke, but what about taking your words literally and using BetterIterable?

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-03-19 at 07:40 PM


But an Ordering enum would be such an incredible burden to implement! Don't bother; have an enum from which you can get an ordering.

As well, remember than any interface we release is something we can never, ever add a method to or otherwise change in basically any way.

"BetterIterable"? Believe me, we considered it. We tried everything. NiceIterable, RichIterable, SuperIterable, Iterabler, Iterablest, IterablePlus, IterablePlusPlus, DeluxeIterable, Iterable2, MintyFreshIterable, OrganicFreeRangeIterable, IterableThatIsTehHotness, PhatIterable, BarackObamaWouldUseThisIterable, Bob . . .

gissuebot commented 9 years ago

Original comment posted by jim.andreou on 2009-03-19 at 08:48 PM


Maybe you should put less weight to API discoverability, and trust your users to know it, and read the documentation (assuming that at some point in time some kind of tutorial will be written, in the spirit of java collections). If this thing is going to be used as widely as java collections, which I believe that at some point it will, it will be common ground for java developers to know its basics, as much you take for granted that java developers know the existence of java.util.Collections utilities.

(A question, in order to understand the precise dilemma: if you introduce FluentIterable, you would remove the similar methods from Iterables? Or keep them redundantly?)

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-03-19 at 08:51 PM


It's too late to have any other choice but to keep them redundantly.

gissuebot commented 9 years ago

Original comment posted by kevinb9n on 2009-09-17 at 06:02 PM


(No comment entered for this change.)


Labels: Milestone-Post1.0

gissuebot commented 9 years ago

Original comment posted by jonhnnyweslley on 2009-11-16 at 12:59 AM


Take a look at ParallelArray: "The ParallelArray library builds on top of fork-join and provides a functional style API for mapping, filtering, reducing, etc over an array of Java objects. Without closure support, the API is not particularly pretty, but I think it’s eminently useful. The decision was made not to standardize it in the JDK yet but you can still download it from the JSR 166 site directly."

http://gee.cs.oswego.edu/dl/jsr166/dist/extra166ydocs/extra166y/ParallelArray.html

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2010-07-30 at 03:53 AM


(No comment entered for this change.)


Labels: -Milestone-Post1.0

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2010-07-30 at 03:56 AM


(No comment entered for this change.)


Labels: -Priority-Medium

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2011-01-12 at 08:33 PM


Issue #520 has been merged into this issue.

gissuebot commented 9 years ago

Original comment posted by nvollmar on 2011-01-13 at 08:15 AM


Do you have any plans to release your internally used FluentIterable in the near future?

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2011-01-13 at 02:57 PM


It is not a high priority, no.


Status: Accepted Owner: ---

gissuebot commented 9 years ago

Original comment posted by nvollmar on 2011-01-19 at 01:04 PM


Just for those interested, I've extended my Queryable with fold and orderBy/thenBy.

It's as lazy as possible. For example: elements.filter(...).transform(...).first() will result in one call to the transformer only (and filter to the first element that returns true).

So I think Queryable isn't a bad name for it, since its more than just fluent.

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2011-07-13 at 06:18 PM


(No comment entered for this change.)


Status: Triaged

gissuebot commented 9 years ago

Original comment posted by tomas.zalusky on 2011-07-19 at 07:26 AM


Hi,

at first thanks for Guava, it helps to increase productivity a lot! My question: please could you describe current state of adding fluent versions of collection-related classes to Guava? I carefully watch these issues and am interested of current situation. I rate this feature very important.

If the only problem is naming, wouldn't be better to ultimately decide somehow? (Personally I prefer concise one-word names like Sequence, Filter/Matcher or Transformer but I absolutely accept anything else - rather unfavorite name than nothing.)

Thanks! Tomáš Záluský

Historical comments:

https://github.com/google/guava/issues/11 Mar 17, 2009 We have been experimenting internally with "fluent" versions of common types.

https://github.com/google/guava/issues/334 Feb 26, 2010 We have great "fluent" versions of Predicate, Function and Iterable, and I hope we can release them soon. The only real problem is that these classes are THE hardest things to name EVER.

https://github.com/google/guava/issues/11 Jan 13, 2011 Do you have any plans to release your internally used FluentIterable in the near future? - It is not a high priority, no.

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2011-07-19 at 02:23 PM


Ah, it is about time for an update. I mentioned that we've been "experimenting" with fluent Iterables, Functions and Predicates inside Google. Most of us feel that the experiment has been a success, and we will endeavor to get these into Guava release 11 or 12 for you.

We will call them simply FluentIterable, FluentFunction and FluentPredicate.


Status: Accepted

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2011-12-09 at 07:08 PM


(No comment entered for this change.)

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2011-12-09 at 07:08 PM


Issue #818 has been merged into this issue.

gissuebot commented 9 years ago

Original comment posted by fry@google.com on 2011-12-10 at 03:12 PM


(No comment entered for this change.)


Labels: Package-Collect

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-01-03 at 07:38 PM


This is super exciting and yay.

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-01-03 at 08:15 PM


Recommendation: having transform() or map() methods on FluentFunction would allow it to be overloaded for list, collection, iterable, optional, etc., as opposed to adding those methods to FluentIterable alone.

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-02-16 at 06:50 PM


We're splitting this up into separate issues; FluentPredicate is now issue 334.

gissuebot commented 9 years ago

Original comment posted by em...@soldal.org on 2012-02-17 at 12:08 AM


Would it be possible to weave functions into a monadic structure using optional?

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-02-17 at 01:00 AM


...Speaking as a Haskell aficionado who thinks monads are awesome...

No. Java does not have the syntax to make it even remotely pleasant...and I'm not sure that even Java 8 will make monads pleasant in Java.

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2012-03-02 at 08:29 PM


Trying for release 12 with this, but not certain.

Here is our API for your consideration:

First, you get a FluentIterable using FluentIterable.from(anyIterable) or FluentIterable.of(T...).

There are simple queries:

There are the "element extraction" methods:

Then the fun stuff: chaining-style methods which all return FluentIterable:

And lastly when you're done with all the chaining stuff, you might want to dump the contents into something else:

Oh, one other thing: it will not have special equals or hashCode behavior, and we are not sure what to do about toString(). Generating AbstractCollection-style output seems useful, but we probably want to cap that at a certain ceiling, against the risk of FluentIterable.from(Ranges.all().asSet(integers())), etc.

What do you think so far, users?


Labels: Milestone-Release12

gissuebot commented 9 years ago

Original comment posted by cgdecker on 2012-03-02 at 09:26 PM


That looks good and is more or less what I'd expect.

A few thoughts:

  • getOnlyElement() -> Optional<T> [equiv. to Iterables.getOnlyElement, but should we try to find a better name?]

This is slightly confusing to me in this context. Does it return absent if the iterable is empty, a value if it has one element, and throw an exception if it has more?

  • append(Iterable) (this is equivalent to Iterables.concat! We are trying to remember why we felt a name change was justified. Your thoughts?)

Maybe it's just me, but I generally think of concat as a standalone operation that takes its inputs and concatenates them (as with Iterables.concat), not something that one object does to add another to it. Append makes sense for that to me; StringBuilder etc. already use it and I think it accurately describes what you're doing. "Append the input iterable to this one to produce a new iterable." Replace "append" with "concatenate" there and it doesn't sound quite right to me.

  • toImmutableList() -> ImmutableList<T>
  • toImmutableSet() -> ImmutableSet<T>
  • toArray() -> T[]

Would it make sense to add a method like this?

That would allow users to easily dump the contents into a mutable collection if desired. On the other hand, maybe it'd be better not to encourage that?

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2012-03-02 at 10:02 PM


We considered copyInto(C) as the direct analog of Iterables.addAll(). It's not ruled out, but we noticed that within Google, SetView.immutableCopy() has 6x as many usages as SetView.copyInto(C). And our internal FluentIterable has become pretty popular and no one's ever asked for it yet.

gissuebot commented 9 years ago

Original comment posted by em...@soldal.org on 2012-03-02 at 11:07 PM


Just a quick question, it produces new object for each method call right rather than mutating itself?

gissuebot commented 9 years ago

Original comment posted by brice.dutheil on 2012-03-03 at 12:51 AM


Hi Kevin,

What do you mean by that ?

it will not have special equals or hashCode behavior

It should at least have the be the same behavior as a list or set of the current FluentIterable.

Also, what do you think of using the 'map()' name intead of 'transform()'. I also would like to see the 'fold' or 'reduce' operator, which makes sense in this scenario.

I think naming this properly is better in the long run, just as we like to indicate patern names in the code. Having these known higher order function naming might actually help users see / understand what functional programming means.

Thoughts ?

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-03-03 at 02:22 AM


It should at least have the be the same behavior as a list or set of the current FluentIterable.

The hashCode and equals behavior of lists and sets is different. FluentIterable really shouldn't have any special equals or hashcode.

I have mixed feelings about map vs. transform. I like that the name is the same for both Iterables and FluentIterable.

Fold or reduce doesn't make sense unless we either introduce a Pair type (which is in the Idea Graveyard and should never happen), or we introduce a binary function type, which I don't think we're ready to do.

gissuebot commented 9 years ago

Original comment posted by wasserman.louis on 2012-03-03 at 02:22 AM


(See issue 218 on fold/reduce.)

gissuebot commented 9 years ago

Original comment posted by brice.dutheil on 2012-03-03 at 01:17 PM


It should at least have the be the same behavior as a list or set of the current FluentIterable.

The hashCode and equals behavior of lists and sets is different. FluentIterable really shouldn't have any special equals or hashcode.

I'm sorry I didn't express what I wanted to say correctly, I wanted to say the current FluentIterable could use the same behavior as the underlying iterable being a List or a Set.

Anyway I'd like to rollback my after-midnight thought, because Iterable can have many different implementations that are not set nor list. I agree with you : no special behavior for equals and hashcode.

I have mixed feelings about map vs. transform. I like that the name is the same for both Iterables and FluentIterable.

Actually without the fold operator. I agree this doesn't make sense to use the map name instead of transform. Though it could be aliases.

I don't think Pair was a good option anyway to implement folding, it feels unnatural. However I would rather see a binary function type, and one of them being an Accumulator type.

gissuebot commented 9 years ago

Original comment posted by kevinb@google.com on 2012-03-03 at 02:51 PM


Emily: yes.

gissuebot commented 9 years ago

Original comment posted by j...@nwsnet.de on 2012-03-05 at 09:04 AM


  • get(int) -> T? (Optional.absent() seems like a bad way to respond to an invalid index, but I don't know if we have a clear enough justification to be different from all the others.)

Why is that a bad way? And what's the alternative? Both returning null and throwing an exception (IllegalArgumentException?) seems suboptimal to me (NPEs sneaking in, and overhead, respectively). The case for getOnlyElement is somewhat different, though, so throwing an exception would justified there.

gissuebot commented 9 years ago

Original comment posted by kurt.kluever on 2012-03-05 at 07:42 PM


(No comment entered for this change.)


Owner: kak@google.com

gissuebot commented 9 years ago

Original comment posted by MrChrisPSimmons on 2012-03-12 at 09:57 AM


We've implemented something similar to this internally and I've got a use case I'd like to see considered that isn't mentioned above.

A means to composite Iterables, similar to Iterables:

<T> Iterable<T> concat(Iterable<? extends Iterable<? extends T>> inputs)

Note that you've got an iterable of iterables here, which naturally maps onto something like this:-

FluentIterable<Out> concat(Function<? super T, Iterable<Out>> function)

If T extends Iterable then the function can just be the identity.

This is much more powerful than append(Iterable) - roughly 3:1 ratio of usage in our code base of a concat-like operation versus append.

gissuebot commented 9 years ago

Original comment posted by kak@google.com on 2012-03-13 at 08:50 PM


Just hit head! https://github.com/google/guava/commit/ec452d24ada3b476ede4bbd9c68e71d4211c0afe

@MrChrisPSimmons + others: Can you please file a separate issue for your requests? Thanks :-)


Status: Fixed

gissuebot commented 9 years ago

Original comment posted by cgdecker on 2012-03-13 at 10:13 PM


Awesome! I added a review for the commit with a couple questions.