1

I am writing a library call that will allow you to filter and map in the same iteration of the loop, this is async/callback style in Java. The problem of course is that for filtering we usually return true/false, but for mapping we need to return the value we want.

So I am thinking of something like this:

(val, v) -> {
   v.include();   // we want to keep this value
   return val * 2;  // the value to map to
}

so the include() method call is like returning true in a typical filter operation. We could add an exclude() method which is returning false.

My question is - is this a good methodology? Are there libraries that do something like this that have a well-understood way of filtering and mapping in the same iteration?

if it's not clear, this would discard the element in the array.

(val, v) -> {
   v.exclude();   
   return null; 
}

another solution would be for them to return a unique key if they want to filter the value out:

(val, v) -> {
   return DISCARD_KEY;  // return a unique key representing discard, like returning false in a typical filter 
}

4 Answers 4

3

In most functional programming languages or libraries, you would do this with a flatMap, and use an empty array (or option) if you want to exclude the item, like in JavaScript:

[5, 6, 7, 8, 9, 10].flatMap(x => (x % 2 == 1) ? [] : x / 2)
// outputs [3, 4, 5]

This removes all the odd numbers and halves all the even numbers.

9
  • I believe it should be [x / 2], unless JavaScript does some kind of type coercion in this case. Mar 1, 2019 at 5:09
  • It does coerce, but you're right that would be technically more correct. Mar 1, 2019 at 5:20
  • so the empty array represents false (filter it out)? Not sure I get it...what if the input array was made up of arrays?
    – user290257
    Mar 1, 2019 at 9:23
  • @MrCholo: flatMap maps elements to arrays, then flattens these arrays, where an empty array is flattened to nothing. Think of flatMap as a more general version of map, in that it can return 0 to N elements, instead of exactly 1 like map. Mar 1, 2019 at 11:38
  • 1
    An option is a type in some libraries that indicates either the presence or absence of a value. For example, Optional in Java. Mar 1, 2019 at 19:54
1

Using Java, I decided to use the java.util.Optional type, which is essentially a wrapper for a value or an absence of a value. If you use this:

Optional.empty(), it's the absence of a value, which means false in our filter scheme.

otherwise I use:

Optional.of(v), where that means true in our filter scheme and the mapped value is v. So the implementation looks something like:

public void done(E e, Optional<T> o) {
  if (o != null && o.isPresent()) {
    this.done(e, o.get());  
  } else {
    this.discard();
    this.done(e, (T) DISCARD_KEY);
  }
}

note that the java.util.Optional wrapper concept could be implemented in literally any language, it's not unique to Java, which is why this answer is pertinent.

0

I would say this pattern creates some ambiguity. What should the result be, for instance, if the caller does not call either include() OR exclude()? There is no chance to catch this situation statically, so either you have to deal with this ambiguity in your library or throw a runtime exception.

As a suggestion, why not make the return value of the callback an action defining what to do with the element?

If we call what you want to do a Milter (Map + Filter), you might end up with something like this (Don't have a compiler here so this may not be picture-perfect):

import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;

public class Test
{
    public static void main(String[] args)
    {
        Milter.Visitor<Integer> visitor = new Milter.Visitor<Integer>() {
            @Override
            public MilterAction<Integer> visit(Integer element) {
                if (element % 2 == 0) {
                    return MilterAction.exclude();
                } else {
                    return MilterAction.include(element + 2);
                }
            }
        };

        Milter<Integer> oddsPlus2Milter = new Milter<Integer>(visitor);

        List<Integer> sourceList = Arrays.asList(1,2,3,4,5,6,7,8,9,10);

        oddsPlus2Milter.run(sourceList).forEach(System.out::println);
    }
}

class Milter<T> {
    public interface Visitor<I> {
        MilterAction<I> visit(I element);
    }

    private Visitor<T> visitor;

    Milter (Visitor<T> visitor) {
        this.visitor = visitor;
    }

    List<T> run(List<T> source) {
        List<T> milteredResult = new ArrayList<T>();
        source.forEach(element -> {
            MilterAction<T> visitResult = this.visitor.visit(element);
            if (visitResult.shouldInclude()) {
                milteredResult.add(visitResult.getValue());
            }
        });

        return milteredResult;
    }
}

class MilterAction<T> {
    public static <I> MilterAction<I> include(I value) {
        return new MilterAction<I>(FilterAction.INCLUDE, value);
    }

    public static <I> MilterAction<I> exclude() {
        return new MilterAction<I>(FilterAction.EXCLUDE, null);
    }

    public enum FilterAction {
        INCLUDE,
        EXCLUDE
    }

    private FilterAction action;
    private T value;

    MilterAction(FilterAction action, T value) {
        this.action = action;
        this.value = value;
    }

    public T getValue() {
        return value;
    }

    public boolean shouldInclude() {
        return this.action.equals(FilterAction.INCLUDE);
    }
}

This way, your compiler can ensure for you that all consumer's of the Milter class provide it with a visitor that always returns an appropriate action.

2
  • well the default return value is true, meaning by default it would be included?
    – user290257
    Mar 1, 2019 at 3:45
  • When you say default return value, I'm not sure I understand what you mean, how do you assign a lambda/Runnable/method reference a "default return value"? Mar 1, 2019 at 4:17
0

Java has the Optional::stream function, and streams have flatMap. These two together will let you write java code that is very similar to the JavaScript code in https://softwareengineering.stackexchange.com/a/387859/3307

values.stream()
      .flatMap( v -> (include(v) ? Optional.empty() : Optional.of(v*2)).stream() )

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.