Closed GoogleCodeExporter closed 9 years ago
I can see this happening eventually. It's a little problematic because so many
functions that have an inverse() function are not actually invertible in the
mathmetical sense (for example, you can convert "1.00" to a double and get 1.0,
then
back to a String and get 1.0), and as a result, the kinds of things you expect
to be
able to do with an invertible function (like Sets.transform(Set)) aren't really
ironclad.
I think we can work out how to warn about the minor risks though.
Original comment by kevin...@gmail.com
on 29 May 2009 at 6:54
I just realized that we have actually implemented this already internally, but
we
have named it "Converter."
How would users feel if our InvertibleFunction class were named Converter
instead?
Also, it's an abstract class instead of an interface so that it can have useful
methods on it like convertAll() (aka transform()). That's inconsistent with
our
other types, but it is quite nice...
Original comment by kevin...@gmail.com
on 17 Sep 2009 at 5:33
Original comment by kevin...@gmail.com
on 17 Sep 2009 at 5:34
Original comment by kevin...@gmail.com
on 17 Sep 2009 at 5:45
Original comment by kevin...@gmail.com
on 17 Sep 2009 at 5:57
Could Converter<T,K> be made to implement InvertibleFunction<T,K>. Then you can
implement an interface as an option. The naming would be fine.
Thanks,
Eric
Original comment by ejwin...@gmail.com
on 17 Sep 2009 at 6:16
Definitely make Converter implements InvertibleFunction.
IdentityFunction is one implementation :).
Original comment by stephen....@gmail.com
on 3 Dec 2009 at 1:51
Original comment by kevinb@google.com
on 30 Jul 2010 at 3:53
Original comment by kevinb@google.com
on 27 Jan 2011 at 1:33
Just a comment from my recent experience implementing something like a
Sets#transform: the tricky part is to specify the bijection contract. I mean
the exact semantics of the methods, not just the signatures.
For example:
public interface InvertibleFunction<A,B> extends Function<A,B> {
InvertableFunction<B,A> reverse();
}
This, without any other specification, implies a bijection A <--> B, i.e. from
*every* A to *every* B (oh, yes, A and B (infinite) sets should have the same
cardinality too). This specification makes it the easiest to implement
Sets#transform, but also the hardest to implement InvertibleFunction correctly.
At least from the perspective of Sets#transform, what is really needed is
something far less, and far easier to implement: a bijection not from *all* A
to all B, but one that needs only to be defined for those A's *in a specific
Set<A>*. The image of that would be a Set<B>, and similarly, the inverse
function would only be required to map *only* the B's in that Set<B> back to
A's of Set<A>.
InvertibleFunction as given above doesn't make it easy to restrict ourselves to
a smaller domain than the type parameter itself (Set<A> is just a subset of A).
Aside: scala functions have an "isDefinedAt(x: A)" method, which is precisely
what I'm talking about. They don't define functions over all A, but only a
subset of it (a predicate on elements of A, such as isDefinedAt, defines a set
of A just as Set<A> does, modulo not providing an iterator).
But we have no isDefinedAt method, and we can't define one, and even if we
could, interfaces with two methods are going to become much more costly than
those with a single method when closures arrive, so I think we are pretty much
stuck with the clumsier approach. The good news is that the clumsier approach
is workable, but the specification of a potential Sets#transform would be
forced to explain this subtlety, that not a complete bijection is required, but
only one defined for the given Set<A> and its image Set<B>, for other inputs it
would be free to return whatever.
Original comment by jim.andreou
on 16 Mar 2011 at 10:46
I cross-posted the above in
http://code.google.com/p/guava-libraries/issues/detail?id=219, sorry for the
mess.
Original comment by andr...@google.com
on 18 Mar 2011 at 10:22
Original comment by fry@google.com
on 22 Mar 2011 at 6:28
Original comment by fry@google.com
on 23 Mar 2011 at 1:49
Just an idea we're kicking around (not a promise):
public abstract class Converter<A, B> extends Function<A, B> {
protected Converter() {}
// I don't love these names. The requirement is that converting
// forward and backward should get you back to a "similar" value,
// but strict invertibility is too much to require.
protected abstract B convertForward(A a);
protected abstract A convertBackward(B b);
@Nullable public final B apply(@Nullable A a) {
return (a == null) ? null : convertForward(a);
}
public final Converter<B, A> inverse() { ... }
public final Converter<A, C> compose(Converter<B, C> next) { ... }
public final Iterable<B> convertAll(Iterable<? extends A> inputs) { ... }
// also suggested, but... these are just slight conveniences for e.g.
// Lists.transform() anyway...
public final Collection<B> convertAll(Collection<? extends A> inputs) { ... }
public final List<B> convertAll(List<? extends A> inputs) { ... }
}
public final class Converters {
public static <T> Converter<T, T> identity() { ... }
}
Note that we always convert null to null. We believe this is easier for
everyone than having it always be up to individual converter interpretation.
And after all, "I don't know how many degrees F it is" is certainly equivalent
to "I don't know how many degrees C it is".
We could add these to, e.g., common.primitives.Ints:
public static Converter<String, Integer> stringConverter() { ... }
We could consider an InvertibleConverter (doesn't that name just roll off the
tongue?) subtype that has the much stricter requirement that whenever 'B b =
c.convertForward(a)' does not throw an exception,
'c.convertBackward(b).equals(a)' *must* be true. This doesn't necessarily
expose any more functionality, it just helps to caution against assumptions
that garden-variety Converters have this property, which so very very many will
not.
For example:
// convertBackward uses name(), not toString()
public static <E extends Enum<E>> InvertibleConverter<String, E>
stringConverter(Class<E> enumClass) { ... }
Comments?
Original comment by kevinb@google.com
on 26 Mar 2011 at 5:40
Here's another suggestion at a top-level interface (which Converter might
implement as a convenience for certain types of conversions).
public interface ReversibleFunction<A, B> extends Function<A, B> {
Function<? super B, ? extends A> reverse();
}
Note the capture-of (?)s in the reverse() signature here. An abstract Converter
class with an exact one-to-one type equivalence could easily override this with
an exact Function<B, A> signature, but it leaves implementers the choice to use
a Function with a slightly different signature for the reverse.
The signature of reverse() here also deliberately returns solely Function, not
ReversibleFunction. Besides the fact that this is required with the
capture-of's, it (1) might not be guaranteed that you could reverse().reverse()
and get exactly the same results, or (2) might be that the ReversibleFunction
has a complicated implementation, but the reverse of it is trivial with a
slightly different signature (say, from a singleton).
Sample combiner utility method that could use the above:
public static <A, B> ReversibleFunction<A, B> join(final Function<A, B>
forward, final Function<? super B, ? extends A> reverse) {
return new ReversibleFunction<A, B>() {
public B apply(A a) {
return forward.apply(a);
}
public Function<? super B, ? extends A> reverse() {
return reverse;
}
};
}
Original comment by tv@duh.org
on 6 Apr 2011 at 1:11
Issue 457 has been merged into this issue.
Original comment by kevinb@google.com
on 6 Apr 2011 at 3:27
I like the idea of a two-way converter between enums and their name
representation.
Not sure about the other details, though (but I trust you on that).
Original comment by j...@nwsnet.de
on 6 Apr 2011 at 7:59
I wonder if the "almost-but-not-quite-a-bijection" problem could be mitigated
by habitually using a class like this one during development:
class CheckedConverter<A, B> extends ForwardingConverter<A, B> implements
InvertibleConverter<A, B> {
protected B convertForward(A a) {
B b = delegate().convertForward();
// Ensure that the conversion is reversible for this value
assert (convertBackward(b).equals(a)); // Or maybe use a method from Preconditions?
return b;
}
protected A convertBackward(B b) ... // similar
}
With a public entry point here:
public class Converters {
// ...
public static <A, B>Converter<A, B> checkedConverter(Converter<A, B> c) {...}
// And maybe:
public static <A, B>Converter<A, B> checkedConverter(Converter<A, B> c, Equivalence<A> e1, Equivalence<B> e2) {...}
// ...
}
I expect I would often find this early warning useful, even if I did not intend
to implement InvertibleConverter in my public API.
Original comment by fin...@gmail.com
on 7 May 2011 at 12:07
Original comment by kevinb@google.com
on 13 Jul 2011 at 6:18
Original comment by kevinb@google.com
on 16 Jul 2011 at 8:37
Original comment by kevinb@google.com
on 5 Oct 2011 at 5:19
Original comment by fry@google.com
on 16 Nov 2011 at 7:33
Original comment by fry@google.com
on 10 Dec 2011 at 3:38
This is being worked on but may miss Guava 12.
Original comment by kevinb@google.com
on 16 Feb 2012 at 6:45
Original comment by wasserman.louis
on 16 Feb 2012 at 7:07
Original comment by wasserman.louis
on 3 May 2012 at 4:17
Original comment by kevinb@google.com
on 30 May 2012 at 7:43
Original comment by cpov...@google.com
on 7 Nov 2012 at 7:39
Original comment by kak@google.com
on 29 Jan 2013 at 6:28
https://code.google.com/p/guava-libraries/source/detail?r=75fda2a9fea9e3415c661e
8688332c24ffddc940
Original comment by cgdecker@google.com
on 19 Dec 2013 at 6:59
This issue has been migrated to GitHub.
It can be found at https://github.com/google/guava/issues/<id>
Original comment by cgdecker@google.com
on 1 Nov 2014 at 4:16
Original comment by cgdecker@google.com
on 3 Nov 2014 at 9:10
Original issue reported on code.google.com by
ejwin...@gmail.com
on 29 May 2009 at 6:48