2

I am reading about functional programming basics. Anyway, I make some examples trying to understand the concept and use it correctly.

I do not get the power of this functional programming. Is it only in writing lambdas instead of normal code?

If we have this class:

public class Dinosaurio {
    private boolean esMamifero;
    private String nombre;
    public Dinosaurio(String n, boolean esMam) {...}
//getters and setters

This functional interface:

@FunctionalInterface
public interface DinosaurioTester {
    boolean test(Dinosaurio d);
}

And this main class:

public class LambdaMain {

    public static void main(String[] args) {

        List<Dinosaurio> lista = new ArrayList<>(); 
        lista.add(new Dinosaurio("Manolo", true));
        lista.add(new Dinosaurio("Pepe", true));
        lista.add(new Dinosaurio("Paco", false));
        lista.add(new Dinosaurio("Curro", true));
        lista.add(new Dinosaurio("Nemo", false));

        pintadorDinosaurios(lista, a->a.isEsMamifero());
    }

    public static void pintadorDinosaurios(List<Dinosaurio> ld, DinosaurioTester dt) {

        for(Dinosaurio d : ld) {
            if(dt.test(d)) {
                System.out.println(d.getNombre());
            }
        }

    }

}

This works great, but I don't see a real advantage from using this:

if(dt.test(d)) {

instead of this:

if(d.isEsMamifero())

Edit: Sure this example is an bad example code for functional programming real usage. But it's all that I can think about it now.

Kedar Mhaswade
  • 4,535
  • 2
  • 25
  • 34
Genaut
  • 1,810
  • 2
  • 29
  • 60
  • What if you want to test a different property of the dinosaur, e.g. you want to print all the dinosaurs named "Manolo"? – Andy Turner Jan 19 '19 at 18:57
  • Possible [duplicate](https://stackoverflow.com/questions/47407180/do-lambda-expressions-have-any-use-other-than-saving-lines-of-code)? Not certain enough to VTC... See my answer to the linked question if that helps. – Jared Smith Jan 19 '19 at 19:01
  • Merely using Lambda expression won’t let you understand the need of Functional programming or more specifically Declarative programming. Well, you can probably write the entire code in Imperative style first & then compare it with the posted code! After that you might get some insight of the power of Functional paradigm of programming. – Abhinav Jan 19 '19 at 19:07
  • @AndyTurner you only have to change d.isEsMamifero() to "Manolo".equals(d.getNombre). Anyway you have to change this line in normal programming and the lambda expression in the functional programming way – Genaut Jan 19 '19 at 19:11
  • 1
    @JaredSmith Thanks. This help me a lot with my question :) – Genaut Jan 19 '19 at 19:12
  • 2
    @Genaut right. But if you don't use the functional parameter, you have to have a method to print the mammals, a method to print the dinosaurs called Manolo, a method to print the dinosaurs whose name is longer than 4 characters.... So instead of changing 1 line, you add 7. – Andy Turner Jan 19 '19 at 19:12
  • @AndyTurner You catch me! Thanks, I continue reading and understanding better all of this. – Genaut Jan 19 '19 at 19:14
  • In addition to @AndyTurner’s comment, using the functional parameter makes the loop so generic, that you don’t have to implement it yourself, i.e. you could have used `lista.stream().filter(a-> a.isEsMamifero()).forEach(System.out::println);` instead of creating the `pintadorDinosaurios` method.Things get even more interesting when you use, e.g. `lista.removeIf(a->a.isEsMamifero());`. This operation will end up at a loop implemented right inside `ArrayList`, being optimized to be `O(n)` instead of the `O(n²)` of an ordinary removing loop. This is only possible due to the predicate abstraction. – Holger Jan 21 '19 at 16:01

2 Answers2

7

This seems to be a very common question, although it has a very easy answer, in my opinion.
For me, it's about how you see the contract that you design and how you expect it to be implemented and used.

As you note, your example shows a bad way of using functional interfaces. Designing these types just to end up calling if(dt.test(d)) instead of if(d.isEsMamifero()) takes a simple adjective: bad. And this is probably imputable to text books. The problem is most books teach us to use functional interfaces the way we're taught to use interfaces/abstraction in general, and that omits the point and bigger picture of functional programming. Of course one needs to know "how" to implement a functional interface (it's an interface after all), but many books don't tell us where to apply them.

Here's how I explain it to myself (in very basic terms):

1 - See a functional interface as "named logic" (to be implemented on the other side of the contract)

Yes, a functional interface is a type, but it makes more sense to look at a functional interface as named logic. Unlike ordinary types like Serializable, Collection, AutoCloseable, functional intefaces like Tester (or Predicate) represent logic (or just code). I know the nuances are are getting subtle, but I believe there's a difference between the traditional OOP abstract "type" and what a functional interface is meant to be.

2 - Isolate the code that implements functional interfaces from the code that consumes it

The problem with using the two in the same component is made obvious in your code. You wouldn't write a functional interface, declare a method that takes one, all just to implement it and pass it to your own method. If you're doing this and only this, you're using abstraction for the wrong reasons, let alone use functional interfaces correctly.

There are tons of examples of proper use of functional interfaces. I'll pick Collection.forEach with Consumer:

Collection<String> strings = Arrays.asList("a", "b", "c");
strings.forEach(s -> System.out.println(s));

How does this differ from your design?

  1. The designer of Collection.forEach stops at the contract that takes a Consumer (they don't use a Consumer just to name/type a parameter to their own method
  2. Collection.forEach is an operation that needs customized logic. Just as with
    s -> System.out.println(s),
    this "custom logic" can be
    s -> myList.add(s)
    or
    s -> myList.add(s.toUpperCase()),
    etc., all as per the designer of the client code (long after the interface was designed). The Collection interface's forEach method orchestrates iteration and allows the caller to supply the logic invoked in each iteration. That separates concerns.

Stream.filter with Predicate is closer to your example, it will be a good idea to contrast how you use Stream.filter to how you used Tester.test in your example.

With that said, there are many reasons for/against functional programming, the above focused on the reason for using (or not using) functional interfaces as per your example (specifically, from the perspective of the developer who writes the contract).

ernest_k
  • 44,416
  • 5
  • 53
  • 99
5

You mix up two terms: functional interfaces and functional programming. Functional interfaces were introduced in Java as a typed language to enable functional programming. There was no good way of declaring anonymous methods (you had to use anonymous classes. And every anonymous class defines a new class which can lead to class pollution when you use many of them.) However, anonymous methods are a very important element of functional programming. So if you think of the power of functional interfaces, you should rather consider the Stream API of Java. It enables you to express code in a declarative style in which you can define what you want instead of how you get it. If you only think of replacing if(d.isEsMamifero()) by if(dt.test(d)), then you do not win very much. But what about that:

public static void pintadorDinosaurios(List<Dinosaurio> ld, DinosaurioTester dt) {
    ld.stream().filter(dt::test).foreach(System.out::println);
}

Now you have parameterized your filter criteria by your interface. The expression says nothing about how you filter. It only says you want one kind of dinosaurs and print them to the console. Whether the expression uses a loop or a recursion is completely hidden. You can call pintadorDinosaurios by pintadorDinosaurios(lista, a->a.isEsMamifero());.
Without functional interfaces, you must write

pintadorDinosaurios(lista, new DinosaurioTester {
    boolean test(Dinosaurio d) {
        return d.isEsMamifero();
    }
});

which looks quite ugly. So functional interfaces in combination with the Stream API enable you to write it shorter in a more comfortable way (if you are used to it).

mmirwaldt
  • 843
  • 7
  • 17