Question

I stumbled upon a little problem today. Consider a little wrapper class:

class Event<T> {
   T value;
   Class<T> type;
   // other fields, getters and setters omitted for brevity
}

Now I was in a situation where I wanted to convert a Event<Long> into a Event<String> while preserving the other fields and updating the type member.

Eventually I ended up with the most simple "solution":

Event<String> new = new Event(old.getValue().toString(), String.class, other, fields);

Having worked with Haskell on my pet projects however, I naturally longed for a function like fmap :: Functor f => (a -> b) -> f a -> f b (read: given a function from a to b and a functor containing something of type a give me a result containing the b) and after finding no standard implementation I set out to write one myself:

interface Functor<T> {
  Functor<S> fmap( Func1<T,S> f );
}

// ... in Event<T>:

Functor<S> fmap( Func1<T,S> f ) { 
  S newValue = f.call(this.value);
  return new Event( newValue, newValue.getClass(), other, fields);
}

Now there is a problem with this solution: after the call to fmap in Java I am left with an instance of type Functor<String> while the same function in Haskell would return a Event<String>.

Is there a way to get my Event back (without unsafely casting it)?

Était-ce utile?

La solution 2

This works reasonably well for me, but it's not quite Functor<T>. It requires that you also specify F, the type for which your functor is an instance:

interface Fn1<A, B> {
  B apply(A a);
}

interface Functor<A, F extends Functor<?, ?>> {
  <B> F map(Fn1<A, B> f);
}

Your Event<A> class would implement Functor like this:

public class Event<A> implements Functor<A, Event<?>> {

  public final A value;

  public Event(A _value) {
    value = _value;
  }

  public <B> Event<B> map(Fn1<A, B> f) {
    return new Event<B>(f.apply(value));
  }

  public String toString() {
    return "Event<" + value.getClass().getSimpleName() + ">(" + value.toString() + ")";
  }
}

If you need a functor for a closed-to-extension class (e.g. Observable from rxjava), you can write one that looks a little more like a type class, however it won't be able to conform to any functor-like interface since Java lacks higher-kinded types:

public class ObservableFunctor {
  public static <A,B> Observable<B> map(Observable<A> fa, Fn1<A, B> f) {
    return new Observable<B>(f.apply(fa.value));
  }
}

Here's a runnable example that uses both Event and Observable from above:

public class FunctorDemo {

  interface Fn1<A, B> {
    B apply(A a);
  }

  interface Functor<A, F extends Functor<?, ?>> {
    <B> F map(Fn1<A, B> f);
  }

  static class ObservableFunctor {
    public static <A,B> Observable<B> map(Observable<A> fa, Fn1<A, B> f) {
      return new Observable<B>(f.apply(fa.value));
    }
  }

  static class Observable<A>  {

    public final A value;

    public Observable(A _value) {
      value = _value;
    }

    public String toString() {
      return "Observable<" + value.getClass().getSimpleName() + ">(" + value.toString() + ")";
    }
  }

  static class Event<A> implements Functor<A, Event<?>> {

    public final A value;

    public Event(A _value) {
      value = _value;
    }

    public <B> Event<B> map(Fn1<A, B> f) {
      return new Event<B>(f.apply(value));
    }

    public String toString() {
      return "Event<" + value.getClass().getSimpleName() + ">(" + value.toString() + ")";
    }
  }

  public static void main(String[] args) {

    Observable<Event<Long>> oe1 = new Observable(new Event(42L));
    System.out.println("oe1: " + oe1.toString()); // oe1: Observable<Event>(Event<Long>(42))

    Observable<Event<String>> oe2 = ObservableFunctor.map(oe1,
      new Fn1<Event<Long>, Event<String>>() {
        public Event<String> apply(Event<Long> e) {
          return e.map(
            new Fn1<Long, String>() {
              public String apply(Long l) {
                return l.toString();
              }
            }
          );
        }
      }
    );
    System.out.println("oe2: " + oe2.toString()); // oe2: Observable<Event>(Event<String>(42))

  }
}

Autres conseils

No, it isn't possible. For this to happen we would need to abstract over the Functor in your interface, something like

interface Functor<T> as F<T> {
    F<S> map(f : Function<T, S>);
}

But Java doesn't allow you to abstract over type constructors, just types. This is called Higher Kinded Types (HKT). Only a few (non-dependent) languages have HKTs, Scala and Haskell are the only two I can think of.

In fact HKTs are necessary to express a lot of abstractions,

  1. Control.Monad
  2. Control.Applicative
  3. Data.Traversable
  4. Data.Foldable
  5. Control.Monad.Trans
  6. Any monad transformers, full stop
  7. Free monads
  8. Lenses/Prisms
  9. Streaming libraries (they're monad transformers)
  10. Almost everything that was in category-extras

All of these involve abstracting over type constructors, not just concrete types.

Licencié sous: CC-BY-SA avec attribution
Non affilié à StackOverflow
scroll top