CBStreams - Java Streams for ColdFusion (CFML)

ortussolutions 927 views 43 slides Nov 27, 2018
Slide 1
Slide 1 of 43
Slide 1
1
Slide 2
2
Slide 3
3
Slide 4
4
Slide 5
5
Slide 6
6
Slide 7
7
Slide 8
8
Slide 9
9
Slide 10
10
Slide 11
11
Slide 12
12
Slide 13
13
Slide 14
14
Slide 15
15
Slide 16
16
Slide 17
17
Slide 18
18
Slide 19
19
Slide 20
20
Slide 21
21
Slide 22
22
Slide 23
23
Slide 24
24
Slide 25
25
Slide 26
26
Slide 27
27
Slide 28
28
Slide 29
29
Slide 30
30
Slide 31
31
Slide 32
32
Slide 33
33
Slide 34
34
Slide 35
35
Slide 36
36
Slide 37
37
Slide 38
38
Slide 39
39
Slide 40
40
Slide 41
41
Slide 42
42
Slide 43
43

About This Presentation

Welcome to the wonderful world of Java Streams ported for the CFML world!The beauty of streams is that the elements in a stream are processed and passed across the processing pipeline. Unlike traditional CFML functions like map(), reduce() and filter() which create completely new collections until a...


Slide Content

CBStreams => Accelerate Your Functional Programming!

WHO AM I?
•Luis Majano
•Computer Engineer
•Born in El Salvador -> Texas
•CEO of Ortus Solutions
•Sandals -> ESRI -> Ortus
@lmajano
@ortussolutions

What are Java Streams
What is CBStreams
Imperative vs Functional Programming
Building Streams
Using Streams
Collecting Streams

What are Java Streams
•Introduced in JDK 8+
•Not I/O Streams
•A data abstraction layer
•Does not store any data, it wraps the data
•Designed to process streams of data elements
•map(), reduce(), filter(), collect()
•Enables functional-style operations on such elements
https://www.oracle.com/technetwork/articles/java/ma14-java-se-8-streams-2177646.html

What is CBStreams
•Port of Java 8+ Streams to CFML Land!
•90% of all Java functionality is there
•Plus some CFML Dynamic Goodness
•Box Module (ColdBox, CommandBox, etc)
https://forgebox.io/view/cbstreams
install cbstreams

Imperative
VS
Functional
Programming

Imperative Programming
•Major OO languages are imperative (C,++,C#, Java)
•Follow a top-down or procedural design to reach a goal
•Each statement changes the state (side-effect) of the program
•Each statement tells the computer what to change and in what order
•Always cons and pros
function isPrime( number ) {
for( var i = 2; i <= sqr( number ); i++) {
if(number % i == 0) return false;
}
return number > 1;
}
isPrime(9220000000000000039) // Output: true

Functional Programming
•Declarative programming
•We tell the computer what things, actions, etc are
•Runtime determines the best way how to do it
•Functions are first class citizens
•No side-effect or iterating state to worry about
•Always cons and pros
function isPrime(number) {
return number > 1 &&
stream
.rangeClosed( 2, sqr( number ) )
.noneMatch( index => number % index == 0 );
}
isPrime( 9220000000000000039 ) // Output: true

Comparing Styles

Why?

Streams Functional Heaven!
•All about functional programming
•Heavy Lambda/Closure usage
•Must focus on the what and not on the how!
•Create a data processing pipeline
•Not for everything, choose wisely….
You have been warned!

Streams Functional Heaven!
var errors = [];
    var errorCount = 0;
    var oFile = fileOpen( filename );
    var thisLine = fileReadLine( oFile );
    while( errorCount < 40 && !isNull( thisLine ) ){
        if( line.startsWith( "ERROR" ) ){
            errors.append( line );
            errorCount++;
        }
        line = fileReadLine( oFile );
    }
var errors = streamBuilder.ofFile( filePath )
        .filter( line => line.startsWith( "ERROR" ) )
        .limit( 40 )
        .collect();
What if I
want to multi-
thread this?
.parallel()

What about CFML Functions?
•They are limited in input, scope & operations
•No short-circuiting operations
•No lazyness, they all fire top-down
•Each operation blocks until it finishes
processing ALL elements
•Creates new arrays/queries/structs for each
new concatenated operation
•What about infinite input or biiiiig files?
•map(), reduce(), each(), filter()

Element Stream

Stream Processing Pipeline

Lazy!

Stream Lazyness!

Lazy Example
var empIds = [ 1, 2, 3, 4 ];
var employee = streamBuilder.new( empIds )
// Convert ID's to Employee Objects, passing function reference
.map( employeeService.findByID )
// only valid employees
.filter( (employee) => !isNull( employee ) )
.filter( function( employee ){ return !isNull (employee); } )
// only salaries > 10000
.filter( (employee) => employee.getSalary() > 100000 )
// Find the first one
.findFirst()
// Return null
.orElse( null );
expect( employee.getSalary() ).toBe( 200000 );
•Stream performs the  map and two  filter operations, one element at a time.
•Since the salary of id 1 is not greater than 100000, the processing moves on to the next
element.
•Id 2 satisfies both of the filter predicates and hence the stream evaluates the terminal
operation  findFirst() and returns the result.
•No operations are performed on id 3 and 4.

Let’s Get Started!
install cbstreams
StreamBuilder@cbstreams
•The StreamBuilder is injected where needed
•Helps you build streams out of native CFML data types
•Strings, Files, Arrays, Structs, Queries, Nulls
•Helps you build infinite or closure based streams
•You can strong type elements for the stream if needed
•For mathematical operations
•int, long, or double

Empty Streams

emptyStream = streamBuilder.new();
emptyStream = streamBuilder.new().empty();
•Simple way to build streams with no elements
•Useful? Maybe…

Building Custom Streams
builder = streamBuilder.builder();
myData.each( function( item ){
    builder.add( item );
} );
myStream = builder.build();
stream = streamBuilder.new()

.of( "a", "hello", "stream" );
stream = streamBuilder.new()

.of( argumentCollection=myData );
•Two approaches:
•builder() - Add your own data via the add() method
•Of( arguments ) - Via an array of arguments

Streams of Characters
stream = streamBuilder.new().ofChars( "Welcome to Streams" );
•Stream of string characters
•Great for parsing, lookups, etc.

File Streams
stream = streamBuilder.new().ofFile( absolutePath );
try{
    // work on the stream
} finally{
    stream.close();
}
•Non Blocking I/O Classes
•Stream of file lines
•Throw any file size to it, I dare ya!

Generate Infinite Streams
// Generate 100 random numbers
stream = streamBuilder.new().generate( function(){
return randRange( 1, 100 );
} ).limit( 100 );
// Seeded iteration
stream = streamBuilder.new().iterate( 40, function( x ){
return x + 2;
} ).limit( 20 );
•Infinite streams of data
•Start with a seed or no seeded results
•Make sure you limit them or wait forever….

Ranged Streams
stream = streamBuilder.new().range( 1, 200 );
stream = streamBuilder.new().rangeClosed( 1, 2030 );
•Create open or closed ranges
•Similar to of() but a whole less typing

Intermediate Operations
•Remember, they are lazy, nothing gets done until a terminator is called.
•Result is always a stream
Operation Description
limit( maxSize ) Limit the stream processing
distinct()
Return only distinct elements
skip( n ) Skip from the first element to n
sorted( comparator ) Sort a stream using a compactor closure
unordered() Return an unordered stream (default)
onClose( closeHandler ) Attach a listener to when the close operation is called
concat( stream1, stream2 ) Concatenates two streams together
peek( action ) Allows you to peek on the element in the order is called
Map( mapper ) Transform the elements into something else
filter( predicate ) Returns a new stream containing only the requested elements
parallel() Convert the stream to a parallel multi-threaded stream

Terminal Operations
•They kick off processing of elements sequentially or in parallel
Operation Description
iterator() Returns a java iterator
spliterator() Returns a java spliterator
close() Close the stream
toArray() Convert the stream back into an array
count() Count the elements in the stream
forEach( action ) Iterate through the elements calling the action closure
forEachOrdered( action ) Iterate through the elements calling the action closure in order, even in parallel
reduce( accumulator, identity ) Fold, reduces the stream to a single element.
max( comparator ) Returns the max value in the stream, if a comparator is passed its called for you
min( comparator ) Returns the min value in the stream, if a comparator is passed its called for you
average( comparator ) Returns the avg value in the stream, if a comparator is passed its called for you
summaryStatistics() Gives you a struct of stats containing: { min, max, count, sum, average }

Short-Circuit Operations
•Also terminal, but can short-circuit processing of the stream
Operation Description
findAny() Find any element in the stream
findFirst() Find the first element in the stream
anyMatch( predicate ) Returns a boolean that indicates if any of the elements match the predicate closure
allMatch( predicate ) Returns a boolean that indicates if ALL of the elements match the predicate closure
noneMatch( predicate ) Returns a boolean that indicates if none of the elements match the predicate closure

Collectors
•Finalizes the stream by converting it to concrete collections
•CBStreams auto-converts Java -> CFML Data Types
Operation Description
collect() Return an array of the final elements
collectGroupingBy( classifier )
Build a final collection according to the classifier lambda/closure that will
classify the keys in the group. End result is usually a struct of data
collectAverage( mapper, primitive=long )
Collect an average according to the mapper function/closure and data
type passed
collectSum( mapper, primitive=long )
Collect a sum according to the mapper function/closure and data type
passed
collectSummary( mapper, primitive=long )
Collect a statistics struct according to the mapper function and data type
passed
collectAsList( delimiter=“,”, prefix, suffix )
Collect results into a string list with a delimiter and attached prefix and/or
suffix.
collectAsStruct( keyId, valueID, overwrite=true )
Collect the elements into a struct by leveraging the key identifier and the
value identifier from the stream of elements to pass into the collection.
collectPartitioningBy( predicate )
partitions the input elements according to a Predicate closure/lambda, and
organizes them into a Struct of <Boolean, array >.

Lambda/Closure References
•CBStreams converts CFML Closures -> Java Lambdas
•Let’s investigate them by Java name:
// BiFunction, BinaryOperator
function( previous, item ){
return item;
}
// Comparator
function compare( o1, o2 ){
return -,+ or 0 for equal
}
// Consumer
void function( item ){
}
// Function, ToDoubleFunction, ToIntFunction,
ToLongFunction, UnaryOperator
function( item ){
return something;
}
// Predicate
boolean function( item ){
return false;
}
// Supplier
function(){
return something;
}
// Runnable
void function(){
// execute something
}

CBStreams Optionals
•Most return values are not the actual values but a CFML Optional
•Wraps a Java Optional
•Simple functional value container instead of doing null checks, with some cool
functions
Operation Description
isPresent() Returns boolean if value is present
ifPresent( consumer ) If value is present call the consumer closure for you
filter( predicate )
If a value is present and the value matches the predicate then return another
Optional :)
map( mapper ) If a value is present, apply the mapping function and return another Optional
get() Get the value!
orElse( other ) Get the value or the `other` if the value is null
orElseGet( other ) Get the value or if not present call the other closure to return a value
hashCode() Unique hash code of the value
toString() Debugging

Examples
myArray = [
    "ddd2",
    "aaa2",
    "bbb1",
    "aaa1",
    "bbb3",
    "ccc",
    "bbb2",
    "ddd1"
];
// Filtering
streamBuilder.new( myArray )
    .filter( function( item ){
        return item.startsWith( "a" );
    } )
    .forEach( function( item ){
        writedump( item );
    } );

Examples
// Sorted Stream
streamBuilder.new( myArray )
    .sorted()
    .filter( function( item ){
        return item.startsWith( "a" );
    } )
    .forEach( function( item ){
        writedump( item );
    } );

Examples
// Mapping
streamBuilder.new( myArray )
    .map( function( item ){
        return item.ucase();
    })
    .sorted( function( a, b ){
        return a.compareNoCase( b );
    }
    .forEach( function( item ){
        writedump( item );
    } );

Examples
// Partition stream to a struct of arrays according to even/odd
var isEven = streamBuilder.new( 2,4,5,6,8 )
.collectPartitioningBy ( function(i){
return i % 2 == 0;
} );
expect( isEven[ "true" ].size() ).toBe( 4 );
expect( isEven[ "false" ].size() ).toBe( 1 );

Examples
// Group employees into character groups
component{
var groupByAlphabet = streamBuilder.of( employeeArray )
.collectGroupingBy( function( employee ){
return listFirst( employee.getlastName(), “” );
} );

expect( groupByAlphabet.get( 'B' ).get( 0 ).getName() )
.toBe( "Bill Gates" );
expect( groupByAlphabet.get( 'J' ).get( 0 ).getName() )
.toBe( "Jeff Bezos" );
expect( groupByAlphabet.get( 'M' ).get( 0 ).getName() )
.toBe( "Mark Zuckerberg" );
}

Examples
// Matching
anyStartsWithA =
    streamBuilder
        .new( myArray )
        .anyMatch( function( item ){
            return item.startsWith( "a" );
        } );
writeDump( anyStartsWithA ); // true
allStartsWithA =
    streamBuilder
        .new( myArray )
        .allMatch( function( item ){
            return item.startsWith( "a" );
        } );
writeDump( anyStartsWithA ); // false

Examples
noneStartsWithZ =
    streamBuilder
        .new( myArray )
        .noneMatch((s) -> s.startsWith("z"));
noneStartsWithZ =
    streamBuilder
        .new( myArray )
        .noneMatch( function( item ){
            return item.startsWith( "z" );
        } );
writeDump( noneStartsWithZ ); // true

Examples
// Reduce
optional =
    streamBuilder
        .new( myArray )
        .sorted()
        .reduce( function( s1, s2 ){
            return s1 & "#" & s2;
        } );
writedump( optional.get() );

Examples
// Parallel Sorted Count
count =
    streamBuilder
        .new( myArray )
        .parallel()
        .sorted()
        .count();

Still in infancy
Implement JDK 9-10 features
CFML Query Support
ORM Integration
ColdBox Core
Reactive Streams
Help me: 

Lucee/Adobe Lambda -> Java lambda
Roadmap

QUESTIONS?
Go Build Some Streams!!
www.ortussolutions.com
@ortussolutions