okaywit / guava-libraries

Automatically exported from code.google.com/p/guava-libraries
Apache License 2.0
0 stars 0 forks source link

Converter<A, B>, which combines a function and its sort-of-inverse #177

Closed GoogleCodeExporter closed 9 years ago

GoogleCodeExporter commented 9 years ago
In several projects I have added the following interface.  I think it would
go very well in Google Collections:

public interface InvertableFunction<T,K> extends Function<T,K> {
    InvertableFunction<K,T> reverse();
}

With an example implementation of:

public class BasicEncryptor implements InvertableFunction<String,String>{
    private final BasicTextEncryptor encryptor;

    public BasicEncryptor(String encryptionKey) {
        this.encryptor = new BasicTextEncryptor();
        encryptor.setPassword(encryptionKey);
    }

    @Override
    public String apply(String rawString) {
        return encryptor.encrypt(rawString);
    }

    @Override
    public InvertableFunction<String, String> invert() {
        return new InvertableFunction<String, String>() {

            @Override
            public InvertableFunction<String, String> reverse() {
                return BasicEncryptor.this;
            }

            @Override
            public String apply(String from) {
                return encryptor.decrypt(from);
            }
        };
    }
}

Original issue reported on code.google.com by ejwin...@gmail.com on 29 May 2009 at 6:48

GoogleCodeExporter commented 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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by kevin...@gmail.com on 17 Sep 2009 at 5:34

GoogleCodeExporter commented 9 years ago

Original comment by kevin...@gmail.com on 17 Sep 2009 at 5:45

GoogleCodeExporter commented 9 years ago

Original comment by kevin...@gmail.com on 17 Sep 2009 at 5:57

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
Definitely make Converter implements InvertibleFunction.

IdentityFunction is one implementation :).

Original comment by stephen....@gmail.com on 3 Dec 2009 at 1:51

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 30 Jul 2010 at 3:53

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 27 Jan 2011 at 1:33

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by fry@google.com on 22 Mar 2011 at 6:28

GoogleCodeExporter commented 9 years ago

Original comment by fry@google.com on 23 Mar 2011 at 1:49

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
Issue 457 has been merged into this issue.

Original comment by kevinb@google.com on 6 Apr 2011 at 3:27

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 13 Jul 2011 at 6:18

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 16 Jul 2011 at 8:37

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 5 Oct 2011 at 5:19

GoogleCodeExporter commented 9 years ago

Original comment by fry@google.com on 16 Nov 2011 at 7:33

GoogleCodeExporter commented 9 years ago

Original comment by fry@google.com on 10 Dec 2011 at 3:38

GoogleCodeExporter commented 9 years ago
This is being worked on but may miss Guava 12.

Original comment by kevinb@google.com on 16 Feb 2012 at 6:45

GoogleCodeExporter commented 9 years ago

Original comment by wasserman.louis on 16 Feb 2012 at 7:07

GoogleCodeExporter commented 9 years ago

Original comment by wasserman.louis on 3 May 2012 at 4:17

GoogleCodeExporter commented 9 years ago

Original comment by kevinb@google.com on 30 May 2012 at 7:43

GoogleCodeExporter commented 9 years ago

Original comment by cpov...@google.com on 7 Nov 2012 at 7:39

GoogleCodeExporter commented 9 years ago

Original comment by kak@google.com on 29 Jan 2013 at 6:28

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago
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

GoogleCodeExporter commented 9 years ago

Original comment by cgdecker@google.com on 3 Nov 2014 at 9:10