Salsa and Java Generics: Understanding the Consumer interface

satya - 9/8/2018, 12:33:34 PM

Salsa for two left feet

Salsa instructors think it is math!

They may make you count, albeit with shuffling feet.

For some they just do it.

Any amount of theory is not going to help those feet to get to the feat.

Same with generics.

You can study all day long and yet their signatures stay theoretical.

They stay theoretical because, the language of generics, is really a set of patterns

Like a real language you don't think in grammar! You just have to know the patterns and forget the language.

So what a generic syntax says you have to internalize the pattern behind it!!!

satya - 9/8/2018, 12:34:32 PM

Consider this picture

satya - 9/8/2018, 12:35:14 PM

Lets read this picture now, and then we will move to say this in generics

Lets read this picture now, and then we will move to say this in generics

satya - 9/8/2018, 4:15:10 PM

Lets talk Tigers first: The object that is being treated

The system above is working with objects of type Tiger.

In the hierarchy a Tiger is an animal

A tiger is not a bird

A tiger is a cat

But a tiger is not a Bengal Tiger! Because Bengal Tiger has additional stuff than a regular tiger. A Bengal Tiger is a tiger but a Tiger is NOT a Bengal Tiger.

satya - 9/8/2018, 4:16:40 PM

The Workers

The system contains a number of workers

Each worker specializes in working with a particular type of creature

These workers are not interchangeable

satya - 9/8/2018, 4:18:32 PM

The Supplier

Now, there is a supplier that has a collection of Tigers. It is a Tiger Supplier.

A Tiger supplier wants to "Give Tiger(s) To" a worker so that the tigers can be processed by the worker that is capable of working with that Tiger.

satya - 9/8/2018, 4:24:04 PM

The "GiveItTo()" function

The supplier will call the "GiveItTo(Worker)" function

At this juncture, what should be nature (type) of the worker?

The rule the supplier may want to express is

1. I want to make sure the worker knows how to work with Tigers. Because I am passing a Tiger.

2. It is ok if the worker knows how to work with Cats, or Animals. Because a Tiger is a cat and also an animal. It is fully type substitutable for cats and animals.

3. If the worker knows to work with birds, then that worker should be rejected.

4. the worker should also be rejected if the worker is in the habit of "Bengal Tigers". Because the worker may expect a BengalTiger but what is passed is less than a BengalTiger.

satya - 9/8/2018, 4:25:29 PM

So what are we saying

Please give it to worker that knows how to work with Tigers or workers that knows how to work with any of its base classes!

satya - 9/8/2018, 4:27:30 PM

How do we say this programmatically


void GiveItToWorker(
    Worker<? super Tiger> worker);

satya - 9/8/2018, 4:28:04 PM

Consider the code below now


private void test1()
   {
      Collection<Person> people =
            Person.createRoster();
      people.stream().forEach((p) -> {
            System.out.println("Test1: Person:" + p);
         }
      );
   }

satya - 9/8/2018, 4:34:52 PM

Here is the signature of forEach() method


interface Stream <T>
{
...
void forEach(Consumer<? super T> action);
...
}

//and here is the Consumer
interface Consumer<T>
{
  void accept(T t);
  ...
}

satya - 9/8/2018, 4:40:27 PM

Some example consumers


Consumer<String> stringConsumer;
Consumer<Object> anyObjectConsumer;
Consumer<Person> personConsumer;
Consumer<Male> maleConsumer;

//So the signature
void forEach(Consumer<? super Person> personConumer)

//will only match
anyObjectConsumer
personConsumer

//but will fail
stringConsumer
maleConsumer

satya - 9/8/2018, 4:42:11 PM

What is this? What kind of a consumer is this?


people.stream().forEach((p) -> {
            System.out.println("Test1: Person:" + p);

Here the lambda function creates a Consumer on the fly based on the lambda syntax.

satya - 9/8/2018, 4:52:57 PM

So that argument type again


//A function in java points to only ONE type: 

f1(String s)

//You cannot say 

f1(String x or MyString y, ...other args)

//But generics allows you to say

f1(Any-Type-Super-class-Of-String  flexibleArg)

//The way you say this is

f1(<? super String> flexibleArg)

//However, however this only applies
//when the "ARG" TYPE ALLOWS parameterization

//For example, String does not allow parameterization

//So it needs ot be

f1(SomeClassThatAllows<? super String> flexArg)

satya - 9/8/2018, 5:23:09 PM

Consider a hierarchy


Male : M
Person: P
Object: O

satya - 9/8/2018, 5:24:42 PM

Now consider a function


f(P)
  -- allows any method on P
  -- allows any method on O
  -- DOES NOT allow any method on M

satya - 9/8/2018, 5:26:37 PM

Consider this


f(P)
  E - stands for allowed execution
  T(E) - stands for some transformed (on P) execution

//what is true in both cases under f(P) is
//so both for E and T(E)

  -- allows any method on P
  -- allows any method on O
  -- DOES NOT allow any method on M

satya - 9/8/2018, 5:29:41 PM

Lets see what happens when pass things to f(P)


//pass M, P or O to f
f(passedInObject M, P, or O)
   E - execution works for P
   E - execution will work for M
       (because Male is a Person)
   E - execution fails for O
       (because method of P are getting called

satya - 9/8/2018, 5:33:52 PM

what happens when transformed things are passed to f(P)


//pass T(M), T(P), T(O)
T(M) - Transform M in someway
T(P) - Transform P in some way
T(E) - Transform O in some way

//Now the rules are same
f(P)
Allowed executions are
   Person methods
   Object methods
   --Male methods are not allowed


f(passedInObject T(M), T(P), T(O))
   T(P) because P can be transformed with Person methods
   T(O) O can be transformed with Object methods
   T(M) Male methods not allowed
   So T(M) fails

satya - 9/8/2018, 5:35:27 PM

When you directly act on an object type

You can pass any sub types.

But you cannot pass super types

Because sub types satisfy the current type and super type methods, which are being used.

satya - 9/8/2018, 5:45:55 PM

When you act on a transformed object type

There is an intermediary transformation

Previously when a super type is passed, we are saying that is not allowed because, we know we are calling methods that the super type doesn't have, the methods of the current type.

But when there is a Transformer that says I only work with "super type" objects, it is guranteeing that only the super type methods are called. Because of this gurantee T(O) is allowed but not O.

Similarly, previously, when sub type is passed, we allowed it because we know we will treat it as current type and will never call its methods.

But when T(M), transformation is in place of the sub type, that transformation will call methods that are not supported on the current type.

So the type substitution reverses!!

satya - 9/8/2018, 6:11:12 PM

there is a lot of confustion

there is a lot of confustion

satya - 9/8/2018, 6:11:19 PM

let me try again

let me try again

satya - 9/8/2018, 6:13:01 PM

Me and My delegated worker, a function says


f(P)
  //work P
  //call P methods
  //call O methods
  //can't call M methods

//obvious so far

//D(p) where D is a delegate
f(P)
  D(P)

//Notice how D is used

satya - 9/8/2018, 6:15:03 PM

So what is the type nature of D(P)?


D(P)
- call P methods
  call O methods
  CANNOT call M methods

D(O)
- call O methods only

D(M)
- Calls M, P, O methods

satya - 9/8/2018, 6:17:04 PM

So...


f(P)
  D(P)

//is equivalent to

f(
  D(P) -- incoming is P.
  D(O) -- incoming is P. So O methods ok
  D(M) -- Not allowed)
)

//So

f(P) = f[ D(P) or D(O) ]

satya - 9/8/2018, 6:18:22 PM

So ...

f(P) can successfully delegate its functionality to those worker objects that promises that they will work with only P or O objects.

So the type signature of f() and D() is quite different!!!

satya - 9/8/2018, 6:26:23 PM

Another way to read this

f(P) is a way to specify incoming object types.

f[ D(P) or D(O) ] is a way to specify type for What code segments are allowed

So

f(O) //Don't allow. Does not impact D(P) or D(O)

f(P) //allow. D(P) or D(O) knows how to work with P

f(M) //allow. D(P) or D(O) knows how to work with P of M

But if you allow D(M) as well then

f(P) will route a P-person object to D(M) - a male code executor and calls male methods on a person object and that can be wrong.

satya - 9/10/2018, 12:02:28 PM

Recasting types as a receiver and sender

satya - 9/10/2018, 12:03:21 PM

Sending as a call to a function

satya - 9/10/2018, 12:04:10 PM

Function calls as a type descendency

satya - 9/10/2018, 12:06:50 PM

Call summary

You can be called with higher specialized types, but you can call functions only with base types or at your own level of type.