Friday, November 17, 2017

Programmers are Lazy and Proud of It - Lambda Expression to the Rescue

Programmers are lazy by nature just like any other field in STEM ( Science, Technology, Engineering, and Math).  Lazy is not a derogatory term in this case,it is the desire to find an easy way to do repetitive and mundane tasks.  Simplification is also the goal for engineers to help other engineers not to make common mistakes.  We can see that trend clearly in loop designs.

First we had while loop that requires Loop Control Variable ( LCV ) priming, boolean logic design, and update to LCV.

        java.util.ArrayList<Integer> me=new java.util.ArrayList<>();
        me.add(1);
        me.add(2);

        int count=0;                                                 //LCV priming
        while(count<me.size()){                             //boolean logic
            System.out.println(me.get(count));    

            count++;                                                  //update
        }


We had a problem since we had to keep track of these three components separately, so in a case of counter controlled loops, a simplified while loop was developed, called for loop.  It is a ternary operator using three arguments, all in one line, to simplify the while loop concept.

        for(int count=0;count<me.size();count++){
            System.out.println(me.get(count1));
        }


We still had problems with boolean logic and +/-1 mistakes processing data.  So, a simplified version of a for loop was developed, called advanced for loop.

        for(int i:me){
            System.out.println(i);
        }


Hiding the complexities and making things simple is an advanced concept, it is a form of oxymoron in geeks world. Thus, having an input, hiding the complexities of processing the input, and having the desired output is an aim for quicker program development with less logical errors or mistakes.  This hiding desire is not a digital age child, it is a mathematical theory and the base for lambda calculus.


Lambda calculus (also written as λ-calculus) is a formal system in mathematical logic for expressing computation based on function abstraction and application using variable binding and substitution. It is a universal model of computation that can be used to simulate any Turing machine and was first introduced by mathematician Alonzo Church in the 1930s as part of his research of the foundations of mathematics.
Lambda calculus consists of constructing lambda terms and performing reduction operations on them. In the simplest form of lambda calculus, terms are built using only the following rules:


The Lambda calculus is an abstract mathematical theory of computation, involving functions. The lambda calculus can be thought of as the theoretical foundation of functional programming. It is a Turing complete language; that is to say, any machine which can compute the lambda calculus can compute everything a Turing machine can (and vice versa).
The notation is based on function abstraction and application based on variable binding and substitution. If you have done same basic programming already, you might be familiar with lambda functions or anonymous functions already. They are featured in Haskell, Mathematica, Python and recent versions of C++ too. In this wiki, we will mostly stick to Haskell.


This concept of process hiding starts by understanding anonymous classes.  As you can see below, class can override methods without changing the original class permanently. 

class Perform{
    public int doubleIt(int i){
        return 2*i;
    }
}
public class HiddenProcess{
    public static void main(String args[]){
        Perform myNumber=new Perform() 
            {
                private String addedAttribute="My Added Message";
                public int doubleIt(int i){
                    return i+i;
                }
            }
;
       System.out.println(myNumber.doubleIt(3));
    }
}

The next concept we need to understand is interfaces.  Interfaces can not be instantiated without a class implementing the interface defined method headers.

interface MyWish{
    void complete(int a);  
}
class MyWishRealize implements MyWish{
    public void complete( int a){
        System.out.println(3);
    }

}
public class HiddenProcess{
    public static void main(String args[]){
        MyWishRealize wishOne=new MyWishRealize();
    }
}
So, we can override and implement methods in anonymous classes therefore we can override methods in classes implementing interfaces.

interface MyWish{
    void complete(int a);  
}
class MyWishRealize implements MyWish{
    public void complete( int a){
        System.out.println(3);
    }
}
public class HiddenProcess{
    public static void main(String args[]){
        MyWishRealize wishOne=new MyWishRealize(){
                public void complete(int a ){
                    System.out.println(3000);
                }
           };

        MyWishRealize wishTwo=new MyWishRealize(){
                public void complete(int a ){
                    System.out.println(6000);
                }
            };

    }
}


So, we can also implement an interface using an anonymous class, so we do not need to create an extra class just to implement the interface.

interface MyWish{
    void complete(int a);  
}
public class HiddenProcess{
    public static void main(String args[]){
        MyWish gift= new MyWish(){
                public void complete( int a){
                    System.out.println("Hello "+a);
                }
            };

    }
}

But the simplified structure can be even more simplified by lambda expression like this;
interface MyWish{
    void complete(int a);  
}
public class HiddenProcess{
    public static void main(String args[]){
        MyWish gift1=(i)->System.out.println("Hello "+i);
    }
}


Lambda Expression 

Prior to Java 1.8, if you looked at the API for ArrayList, you did not have a method to process the array with forEach mehod.

void
ensureCapacity(int minCapacity)
Increases the capacity of this ArrayList instance, if necessary, to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
E
get(int index)
Returns the element at the specified position in this list.
int
Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.

Java 1.8 added that method to the available method's list to simplify processing the array.

void
ensureCapacity(int minCapacity)
Increases the capacity of this ArrayList instance, if necessary, to ensure that it can hold at least the number of elements specified by the minimum capacity argument.
void
forEach(Consumer<? super E> action)
Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception.
E
get(int index)
Returns the element at the specified position in this list.
int
Returns the index of the first occurrence of the specified element in this list, or -1 if this list does not contain the element.


forEach method is based on the iterable interface and takes a consumer interface as its parameter.·         

forEach


public void forEach(Consumer<? super E> action)

Description copied from interface: Iterable

Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception. Unless otherwise specified by the implementing class, actions are performed in the order of iteration (if an iteration order is specified). Exceptions thrown by the action are relayed to the caller.

Specified by:

forEach in interface Iterable<E>

Parameters:

action - The action to be performed for each element


Iterable inerface's forEach method gives us the reference to the enhanced for loop and to the idea of simplifying its operation.·       

 forEach


default void forEach(Consumer<? super T> action)

Performs the given action for each element of the Iterable until all elements have been processed or the action throws an exception. Unless otherwise specified by the implementing class, actions are performed in the order of iteration (if an iteration order is specified). Exceptions thrown by the action are relayed to the caller.

Implementation Requirements:

The default implementation behaves as if:

 
     for (T t : this)
         action.accept(t);
 

Parameters:action - The action to be performed for each element

      Throws:NullPointerException - if the specified action is null

         Since:1.8

The Consumer interface has the method accept that takes any dataType object and performs the given operation on that dataType.  Since Consumer is an interface, a class needs to implement its method accept. 

java.util.function

Interface Consumer<T>

Type Parameters: 
      T - the type of the input to the operation

All Known Subinterfaces:Stream.Builder<T>

Functional Interface:This is a functional interface and can therefore be used as the assignment target for a lambda expression or method reference.

Modifier and Type
Method and Description
void
accept(T t)
Performs this operation on the given argument.



The following code demonstrates the implementation of Consumer interface. ( Note: For simple introduction to Java's Generics - read my article - http://intro2cs-java.blogspot.com/2017/03/generic-getting-started.html )

class implementConsumer<T> implements java.util.function.Consumer<T> {
 public void accept(T i){
     System.out.println(i);
    }

}
public class HiddenProcess{
    public static void main(String args[]){
        implementConsumer<String> value=new implementConsumer<>();
        value.accept("Hello");

    }
}


So, how can we do the same without using the extra class implementConsumer?  WE should use anonymous inner class to accomplish that for this interface. But we would need to create an implementation for each dataType and that would be very annoying.

public class HiddenProcess{
    public static void main(String args[]){
        java.util.function.Consumer<String> value=new java.util.function.Consumer<String>(){
                public void accept(String i){
                    System.out.println(i);
                }
            };

        value.accept("Hello");
    }
}


We would need to create an implementation for ArrayList for each dataType it is instantiated and that would annoy us as well.

public class HiddenProcess{
    public static void main(String args[]){
        java.util.ArrayList<Integer> me=new java.util.ArrayList<>();
        me.add(1);
        me.add(2);
        java.util.function.Consumer<java.util.ArrayList<Integer>> value=new       java.util.function.Consumer<java.util.ArrayList<Integer>>(){
                public void accept(java.util.ArrayList<Integer> i){
                   for(int val:i)
                    System.out.println(val);
                }
            };

        value.accept(me);
    }
}

At the end, we can simplify all this with our lambda expression that works for every dataType and the syntax is simplified to a single line of code.  Isn't that cool!!!

public class HiddenProcess{
    public static void main(String args[]){
        java.util.ArrayList<Integer> me=new java.util.ArrayList<>();
        me.add(1);
        me.add(2);
        me.forEach(i->System.out.println(i));
    }
}

Conclusion

Being lazy means innovation toward an advanced society, so be proud of it and keep innovating!

3 comments:

  1. Hello professor, I am working of a program which include a class called Badges. All my classes are in one package, while my source files are in other package, called "resources". I really find it hard to read from the files without using the absolute path. The Badges class has method as bellow

    public void loadDataFromFile(String fileName) {

    try{
    Scanner sc = new Scanner(new File(fileName));
    while(sc.hasNext()){
    //add the data to file.
    }
    }catch(FileNotFoundException e){
    System.out.println("Connot Find file for :" + fileName);
    }
    }

    There are four other classes, such Employee, Student... that extends to this Badges class. When I create object that I pass the file name to loadDataFromFile().
    none of these code for me. For instance,

    Employee emp = new Employee();
    emp.loadDataFromFile("TableViewExample/resource/Employee.txt");
    emp.loadDataFromFile("/resource/Employee.txt");
    emp.loadDataFromFile("Employee.txt");

    ** TableViewExample is the name of class where main method is.

    ReplyDelete
    Replies
    1. This comment has been removed by the author.

      Delete
    2. It is a function of the project's properties settings. You need o make sure to add the resource folder properly so the resource will be copied into the bin folder by the IDE.

      You can print out and see if you can read the file path like this.
      System.out.println(TestResources.class.getResource("input.txt").getPath());

      or You can create a file object from the file in the resource folder like this.
      ClassLoader classLoader = getClass().getClassLoader();
      java.io.File file = new java.io.File(classLoader.getResource(fn).getFile());

      Delete