Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Java generics - implementing higher order functions like map

I decided to write some common Higher Order Functions in Java (map, filter, reduce, etc.) that are type safe via generics, and I'm having problems with wildcards matching in one particular function.

Just to be complete, the functor interface is this:

/**
 * The interface containing the method used to map a sequence into another.
 * @param <S> The type of the elements in the source sequence.
 * @param <R> The type of the elements in the destination sequence.
 */
public interface Transformation<S, R> {

    /**
     * The method that will be used in map.
     * @param sourceObject An element from the source sequence.
     * @return The element in the destination sequence.
     */
    public R apply(S sourceObject);
}

The troubling function is like a map, but instead of transforming a Collection it transforms a Map (at first I thought it should be called mapMap, but it sounded so stupid that I ended up calling it remapEntries).

My first version was (and take a sit, because the signature is quite a monster):

    /**
     * <p>
     * Fills a map with the results of applying a mapping function to
     * a source map.
     * </p>
     * Considerations:
     * <ul>
     * <li>The result map must be non-null, and it's the same object what is returned
     * (to allow passing an unnamed new Map as argument).</li>
     * <li>If the result map already contained some elements, those won't
     * be cleared first.</li>
     * <li>If various elements have the same key, only the last entry given the
     * source iteration order will be present in the resulting map (it will
     * overwrite the previous ones).</li>
     * </ul>
     *
     * @param <SK> Type of the source keys.
     * @param <SV> Type of the source values.
     * @param <RK> Type of the result keys.
     * @param <RV> Type of the result values.
     * @param <MapRes>
     * @param f The object that will be used to remapEntries.
     * @param source The map with the source entries.
     * @param result The map where the resulting entries will be put.
     * @return the result map, containing the transformed entries.
     */
    public static <SK, SV, RK, RV, MapRes extends Map<RK, RV>> MapRes remapEntries(final Transformation<Map.Entry<SK, SV>, Map.Entry<RK,RV>> f, final Map<SK, SV> source, MapRes result) {
        for (Map.Entry<SK, SV> entry : source.entrySet()) {
            Map.Entry<RK, RV> res = f.apply(entry);
            result.put(res.getKey(), res.getValue());
        }
        return result;
    }

And it seems to be quite correct, but the problem is that the transformation used must match exactly the type parameters, making difficult to reuse map functions for types that are compatible. So I decided to add wildcards to the signature, and it ended up like this:

public static <SK, SV, RK, RV, MapRes extends Map<RK, RV>> MapRes remapEntries(final Transformation<? super Map.Entry<? super SK, ? super SV>, ? extends Map.Entry<? extends RK, ? extends RV>> f, final Map<SK, SV> source, MapRes result) {
    for (Map.Entry<SK, SV> entry : source.entrySet()) {
        Map.Entry<? extends RK, ? extends RV> res = f.apply(entry);
        result.put(res.getKey(), res.getValue());
    }
    return result;
}

But when I'm trying to test it, wildcard matching fails:

@Test
public void testRemapEntries() {
    Map<String, Integer> things = new HashMap<String, Integer>();
    things.put("1", 1);
    things.put("2", 2);
    things.put("3", 3);

    Transformation<Map.Entry<String, Number>, Map.Entry<Integer, String>> swap = new Transformation<Entry<String, Number>, Entry<Integer, String>>() {
        public Entry<Integer, String> apply(Entry<String, Number> sourceObject) {
            return new Pair<Integer, String>(sourceObject.getValue().intValue(), sourceObject.getKey()); //this is just a default implementation of a Map.Entry
        }
    };

    Map<Integer, String> expected = new HashMap<Integer, String>();
    expected.put(1, "1");
    expected.put(2, "2");
    expected.put(3, "3");

    Map<Integer, String> result = IterUtil.remapEntries(swap, things, new HashMap<Integer, String>());
    assertEquals(expected, result);
}

The error is:

method remapEntries in class IterUtil cannot be applied to given types
  required: Transformation<? super java.util.Map.Entry<? super SK,? super SV>,? extends java.util.Map.Entry<? extends RK,? extends RV>>,java.util.Map<SK,SV>,MapRes
  found: Transformation<java.util.Map.Entry<java.lang.String,java.lang.Number>,java.util.Map.Entry<java.lang.Integer,java.lang.String>>,java.util.Map<java.lang.String,java.lang.Integer>,java.util.HashMap<java.lang.Integer,java.lang.String>

So, any hints on how to fix this? Or should I give up and write explicit loops for this? ^_^

like image 424
fortran Avatar asked Jan 26 '11 10:01

fortran


People also ask

Is map a higher-order function?

In many programming languages, map is the name of a higher-order function that applies a given function to each element of a collection, e.g. a list or set, returning the results in a collection of the same type. It is often called apply-to-all when considered in functional form.

Does Java support higher-order functions?

Any time we pass a lambda expression to a method, that method is a higher-order function. Java 8 supports higher-order functions, along with lambda expressions and functional interfaces.

How do you use generics on a map?

use generics. Generic Map in simple language can be generalized as: Map< K, V > map = new HashMap< K, V >(); Where K and V are used to specify the generic type parameter passed in the declaration of a HashMap.

What is a generic map?

Description: A generic map gives the value to a generic. Usually given in an instance but can also appear in a configuration. The values can be given via positional association or via named association. Use of named association is advised to improve readability and reduce the risk of making errors.

What are the higher order functions in Java?

In this tutorial, we will learn about the Higher Order Functions in java. Higher-Order Functions are the functions that either take a function as a parameter or return the function as output. We can say that the Higher-Order functions are the functions that operate on the other function. We will take two examples of the Java Higher-Order Functions.

How to use generics in Java?

For eg, all the inbuilt collections in java like ArrayList, HashSet, HashMap, etc. use generics. Generic Map in simple language can be generalized as: Map< K, V > map = new HashMap< K, V > (); Where K and V are used to specify the generic type parameter passed in the declaration of a HashMap.

What is a generic map in Java?

The term generic simply is the idea of allowing the type (Integer, Double, String, etc. or any user-defined type) to be the parameter to methods, class, or interface. For eg, all the inbuilt collections in java like ArrayList, HashSet, HashMap, etc. use generics. Generic Map in simple language can be generalized as:

What are higher-order functions in iOS and how to use them?

As iOS developers, we must aware of Higher-order functions. Using higher-order functions in our code enhances the execution speed of our code and speed up our development skills. A higher-order function can be defined as a function that accepts one or more functions as arguments and returns a function as a result.


1 Answers

I think you should take a look to Google Guava API.

There you can find a Function interface similar to your Transformation one. There is also a class Maps with utility methods to create or transform map instances.

You should also consider PECS when implementing methods for generics use.

like image 173
hhbarriuso Avatar answered Sep 28 '22 16:09

hhbarriuso