Hacker Newsnew | past | comments | ask | show | jobs | submitlogin

In Java we would call it "Mappable". A Mappable interface would have one method: map(). It hasn't been all that useful until recently due to lack of closures, but perhaps it would make sense in Java 8?

"Functor" is a terrible name that only a mathematician could like.



Just be thankful they didn't name it "Carnap" after the guy who introduced the idea. At least "functor" is moderately descriptive.


Remind me: can you declare a Java interface with a method signature that returns the same type as the class that eventually implements the interface? You wouldn't want the map method on mappable to simply return another Mappable, because that could be any other Mappable (i.e. a CustomList could implement map to return a CustomTree, when the functor concept requires it to return another CustomList).


You can do that partially. Because of the sub-classing and covariant return-types, I think you cannot prevent a CustomCollection from returning a CustomList. The definition would be:

  public interface Mappable<T extends Mappable<T>> {
      T map();
  }


I would bail on type checking and use an assertion. It's not reasonable to try to statically check everything in Java that you could in some other languages.


You can't do it directly, but there is a standard idiom for doing something like that - define the interface like this:

  interface Functor<A, R extends Functor<A, R>>
And then in the implementing class, bind R to the implementing class:

  class Option<A> implements Functor<A, Option<A>>
However, this doesn't let you write an actual functor. Try to write the map method on it:

    class Option<A> implements Functor<A, Option<A>> {
        private final A value;
    
        public Option(A value) { this.value = value; }
    
        @Override
        public <B> Option<A> map(Function<A, B> fn) {
            // er, i want to return an Option<B>, not an Option<A>
        }
    }
The problem is that you don't want to return an instance of the type of the receiver, you want to return an instance of a similar type with a different type parameter (Option<B> rather than Option<A>).

You would be absolutely fine if your functions were functions from values of some type to other values of that type (eg functions from integers to integers), because then A = B and you can get away with this. But there's no way to extend this hack to let you return an Option<B> from map.

The closest you can get in Java is probably this:

    interface Functor<A, R extends Functor<?, R>>

    class Option<A> implements Functor<A, Option<?>> {
        private final A value;
    
        public Option(A value) { this.value = value; }
    
        @Override
        public <B> Option<?> map(Function<A, B> fn) {
            return new Option<B>(value != null ? fn.apply(value) : null);
        }
    }
There, you leave the element type of the returned functor undefined, as a wildcard, which lets you slip an Option<B> out as a return value.

The problem with that is that it's useless. Given:

        Option<String> x;
        Function<String, Integer> fn;
Then mapping the function over the option gives you:

        Option<?> y = x.map(fn);
An Option<?>. Which is no use to man or beast, because it's lost the type information.

What you really want to be able to write is:

  class Option<A> implements Functor<A, Option<_>>
Where the underscore is borrowed from Scala to mean "nothing to see here, move along please", and leaves that reference to Option in its unbound form. And then have a way of binding it right in the definition of the map function. But that's higher-kinded types, and Java doesn't have that.

If you were absolutely desperate to do this in Java, like if terrorists burst in and put a gun to your head and told you to do it, you could restore the type information by pebble-dashing the functor with some simple reflection:

    interface Functor<A, R extends Functor<?, R>> {
        <B> R map(Function<A, B> fn);
        <B> Functor<B, R> as(Class<B> b);
    }

    class Option<A> implements Functor<A, Option<?>> {
        // other stuff as above
    
        @Override
        public <B> Option<B> as(Class<B> b) {
            return new Option<B>(b.cast(value));
        }
    }
Which, given the above definitions of x and fn, lets you write:

        Option<Integer> y = x.map(fn).as(Integer.class);
And even the same thing but typed as functors, to show you there's nothing up my sleeve:

        Functor<String, Option<?>> fx = x;
        Functor<Integer, Option<?>> fy = fx.map(fn).as(Integer.class);
And as a separate function which contains no mention of the concrete functor class anywhere (although it does still need a type token for the result functor's parameter):

    private <A, B, F extends Functor<?, F>> Functor<B, F> applyAbstractly(Functor<A, F> fx, Function<A, B> fn, Class<B> b) {
        return fx.map(fn).as(b);
    }
But to be honest, all of this is a bit like trying to carve a fine wooden sculpture with a chainsaw. If you want to do this, don't use Java. If you want to use Java, don't do this.


What discipline did the word "map" come from? ;) Is "map" even descriptive? In a programming concept, the first time I heard it I thought it was referring to the data structure of the same name, which is pretty different.


Yes, good point. I suppose replaceEach() would be a more descriptive name, so the interface might be HasReplaceEach. It's not as easy to talk about, though. Still better than "functor".

(The function being passed in to map() is an alternate way to represent a very large map, in a mathematical sense.)


Exactly. We'd only call the interface 'Mappable' if the method was called 'map', and that's something that's come from mathematical terminology.

(I'd probably have called the 'Map' class 'Dictionary', if that hadn't already been taken!)

I'd avoid 'replaceEach', because it sounds like it mutates the receiver. Maybe 'withEachReplaced'? Way clunky. In Smalltalk, this method is called 'collect', which is better than 'map', to my eyes, but still not all that obvious [1].

[1] Smalltalk has the names collect, inject, select, and reject for its map, foreach, filter, and complementary filter methods, because of a song: http://smalltalkzen.wordpress.com/2011/02/02/arlo-guthrie-an...


The Smalltalk version of foreach() is called #do:. #inject: is like reduce().

That blog post inspired a fun discussion in the Smalltalk community on what the semantics of #infect: and #neglect: should be!


Mathematicians prefer obscure technical terms to misleading and restrictive metaphors, because obscure terms force you to refer solely to (and eventually internalise) a precise and abstract definition.

I think this is the case for functors, because I've yet to see a metaphor for them which isn't misleading or restrictive. "replaceEach", for instance, would confuse me. I like working with parser combinators, where parsers are functors. If I replaceEach a parser, what have I replaced? The results of the parse? The parser hasn't been run on an input yet. Maybe it never will.

If I replaceEach a promise, what am I replacing? The result of the promise? Maybe the promise will be forever blocked, or maybe I'll cancel it.

If I replaceEach a continuation, what am I replacing?


> because obscure terms force you to refer solely to (and eventually internalise) a precise and abstract definition.

Unless some C++ people decide that “functor” is a wonderful name for a stateful function-like thing which somewhat resembles closures/lambdas but has nothing whatsoever to do with the functors from functional programming.


Well, yes, mathematicians like to be very precise about exactly what a very general abstraction entails, to the point where you are being very precise about how two almost entirely different concepts have almost but not quite nothing in common.

These fine distinctions don't belong in code meant for a general audience. Instead, don't try to generalize so much. It's not important (and in fact it's confusing) that replaceEach can be generalized to work with a parser. Parsers can have a different function with its own name and nobody needs to know that it's sort of the same concept.




Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: