SQLplus prettyprint

Si aneu al SQLplus i feu una query com

select * from all_tables;

Us acaba sortint una cosa illegible. Per solucionar-ho proveu

set pagesize 0
set wrap off
set linesize 60

Una altra solució molt interessant és exportar-ho a un fitxer html extern (out.html per exemple)

set pagesize 1000
set wrap off
SET MARKUP HTML ON
spool  out.html

Hi ha moltes opcions per fer-ho més llegible:

https://docs.oracle.com/cd/A87860_01/doc/server.817/a82950/ch4.htm

Comparativa de llenguatges

Vist a http://rosettacode.org/wiki/Category:Programming_Tasks


main
    Java:   public MyClass {  public static void main(String[] args) {  System.out.println("hola"); } }
    C#:     using System;  class MyClass { public static void Main() {  Console.WriteLine("hola"); } }
    JS:     function f() { print("hola"); }    f()
    Python: def f():   print("hola")    if __name__ == "__main__":   f()
    Kotlin: fun main(args: Array<String>) { println("hola"); } 
    Scala:  object MyObj extends App { def foo { println("hola"); }  foo }

Biblioteques
   Java:   package a.b.c;   import a.b.c;
   C#:     using a.b.c;     namespace a.b.c { ... }
   JS:     const pali = require('../lib/palindrome');
   Kotlin: 
   Python: import doctest
   Scala:  import org.scalacheck._


Strings
    Java:    String doubleQuotes = "\"\"";  
    C#:      string doubleQuotes = "\"\"";
    JS:      var doubleQuotes = "\"\"";  var doubleQuotes = '""';
    Python:  doubleQuotes = "\"\"";   doubleQuotes = '""';
    Kotlin:  val doubleQuotes = "\"\"";
    Scala:   val doubleQuotes = "\"\"";  

Strings multiline
    Java:     String multiline = " ... \n ...";  
    C#:       string doubleQuotes = @" ... ";
    JS:       var multiline = """ ... """;
    Python:   multiline = """ ... """;  multiline =''' ... '''
    Kotlin:   val multiline = """ ... """;
    Scala:    val multiline = """ ... """;

Boolean
    Java:     (a && b) || (!c ^ d)
    C#:       (a && b) || (!c ^ d)
    JS:       (a && b) || (!c)
    Python:   (a and b) or (not c)
    Kotlin:   (a and b) or (!c xor d)   però també  (a && b) || (!c ^d)
    Scala:    (a && b) || (!c)

Foreach
    Java:     for (String thing : things) { ... }
    C#:       foreach (string thing in things) { ... }
    JS:       for (var thing in things) { ... }
    Python:   for thing in things: 
    Kotlin:   for (thing : things) { ... }
    Scala:    for (thing <- things) { ... }

For
    Java:     for (int i=0; i< 4; i++) { ... }
    C#:       for (int i = 0; i < 5; i++) { ... }
    JS:       for (var i=0; i< 4; i++) { ... }    o  range(5).forEach( func )
    Python:   for i in range(1,6): 
    Kotlin:    for (i in 1..4) { ... }   o  (1..4).forEach { ... }
    Scala:    for (i <- 1 to 4) { ... }


While
    Tots:    while ( condicio ) { ... }
    Python:  while n > 0 :


Try/catch
    Java:  try { throw new Exception(); } catch (Exception e) { ... } finally { ... }
    C#:    try { throw new Exception(); } catch (Exception e) { ... } finally { ... }
    JS:    try { throw(new Exception()); } catch (e if e instanceof Exception) { ... } finally { ... }
    Python: try: ... raise Exception ... except Exception: ...
    Kotlin: try { throw Exception(); } catch (e : Exception) { ... } finally { ... }
    Scala:  try { new Exception } catch { case e: Exception => ... }


String compare
   Java:  str1.equals(str2)
   C#:    string.Compare( str1, str2 )
   JS:    str1 === str2
   Python: str1 == str2
   Kotlin: str1 == str2
   Scala:  str1 == str2
   

Object ref compare
   Java:  str1 == str2
   C#:    
   JS:
   Python:
   Kotlin: str1 === str2
   Scala:  str1 eq str2

Arrays
    Java:   String[][] funny_matrix = new string[][]{ {"clowns", "are"} , {"not", "funny"} };
    C#:     string[,] funny_matrix = new string[2,2]{ {"clowns", "are"} , {"not", "funny"} };
    JS      var  matrix =  [["clowns", "are"] , ["not", "funny"]]
    Kotlin: var  matrix = arrayOf( 1,2,3,4 );
    Python: matrix =  [["clowns", "are"] , ["not", "funny"]]
    Scala:  val b = Array("foo", "bar", "baz")    

Lists
   Java:
   C#:     List<int> list = new List<int>();  list.Add(10);   print(list[0]) ;
   JS:       
   Kotlin:
   Python: list = []   list.append(10)     list[0]



Function
   Java:  static double mult( double x, double y ) { return x*y; }
   C#:    static double mult( double x, double y ) { return x*y; }
   JS:    function mult(a, b) { return a*b; }
   Kotlin: fun mult(a: Int, b: Int): Int { return a * b }
   Python: def multiply(a, b): return a * b 
   Scala:  def multiply(a: Int, b: Int) = a * b

Lambda Function
   Java:  
   C#:    Func<double, double, double> multiply = ((a,b) => a*b);
   JS:    var multiply = (a, b) => { return a * b };
   Kotlin:
   Python: multiply = lambda a, b: a * b
   Scala:
 
Paràmetres opcionals
   Java:   addWidget(AddWidgetParams.Builder("root").x(100).y(35).build());
   C#:     void AddWidget(string parent, float x = 0, float y = 0, string text = "Default"); 
             AddWidget("root", 320, 240, "First"); 
             AddWidget("root", text: "Footer", y: 400);
   JS:     function addWidget(parent, options) { opts = {}; opts.x = options.x || 0;  opts.y = options.y || 1; opts.text = options.text || 'pork chops'}
             addWidget("root", {text: "lamb kebab", x: 3.14});
   Kotlin: fun someFunction(first: String, second: Int = 2, third: Double);
             someFunction("positional", 1, 2.0);  
             someFunction(first = "named", second = 1, third = 2.0)
   Python: def show_args(parent, text = 'default value', *posparam, **keyparam): ...
             show_args('POSITIONAL', 'OPTIONAL')
             show_args(text='OPTIONAL', parent='KEYWORD')
             show_args('POSITIONAL', 'OPTIOMAL', 'EXTRA1', 'EXTRA2', 'EXTRA3',  kwa1='EXTRA', kwa2='KEYWORD', kwa3='ARGUMENTS')
   Scala:  def add(x: Int, y: Int = 1) = x + y
             add(5)
             add(y=10, x=4)


Function in/out parameters
    C#:    void Foo(ref int Value) { Value += 1 }  ;  int p=1;  Foo(ref p);




Classes

Java:
------------

  public class Being implements Serializable {
    public boolean isAlive() { return alive; } 
    public void setAlive(boolean alive) { alive= alive; } 
    private boolean alive;
  }
 
  public class Animal extends Being implements Serializable {
    public Animal() { }
 
    public Animal(long id, String name, boolean alive ) {
      Id = id;
      Name = name;
      Alive = alive;
    }
 
    public long getId() { retrun id; }
    public void setId(long id) { id= id; }

    public String getName() { return name; }
    public void setName(String name) { name=name; }

    long id;
    String name;
 
    public void print() { System.out.println( String.format("{0}, id={1} is {2}",
      Name, Id, Alive ? "alive" : "dead")); }
  }

  Animal animal= new Anima( 1, "starky", true );







C#:
--------------


namespace Object_serialization
{
  [Serializable] public class Being
  {
    public bool Alive { get; set; }
  }
 
  [Serializable] public class Animal: Being
  {
    public Animal() { }
 
    public Animal(long id, string name, bool alive = true)
    {
      Id = id;
      Name = name;
      Alive = alive;
    }
 
    public long Id { get; set; }
    public string Name { get; set; }
 
    public void Print() { Console.WriteLine("{0}, id={1} is {2}",
      Name, Id, Alive ? "alive" : "dead"); }
  }
} 
 

Animal animal= new Animal( 1, "starky" )




Python:
------------------

class Entity:
	def __init__(self):
		self.name = "Entity"
	def printName(self):
		print self.name
 
class Person(Entity): #OldMan inherits from Entity
	def __init__(self): #override constructor
		self.name = "Cletus" 


instance1 = Person()
 
instance2 = Entity()



Comparativa de llenguatges de programació

M’agradaria comparar els següents llenguatges

  • Java
  • Javascript
  • .Net
  • Python
  • Scala

M’agradaria fer una taula comparant coses bàsiques com

  • Assignació strings
  • Assignació númerica
  • Com es treballa amb taules
  • Com es treballa amb llistes
  • Bucles
  • Condicions
  • Com es treballa amb diccionaris
  • Com es fa el main()
  • Com es treballa amb classes

I amb coses més complexes com

  • Com es treballa amb Annotations / Decorations
  • Com se treballa amb SQL
  • Com es treballa amb aplicacions visuals
  • Com es treballa amb aplicacions web HTML
  • Com es treballa MVC
  • Expressions Lambda

Catalan numbers en Scala

Demostració de com implementar la seqüència de números Catalans amb Scala. Vist a Rosetta code.


object Catalan {
  def factorial(n: BigInt) = BigInt(1).to(n).foldLeft(BigInt(1))(_ * _)
  def catalan(n: BigInt) = factorial(2 * n) / (factorial(n + 1) * factorial(n))
 
  def main(args: Array[String]) {
    for (n <- 0 to 15) {
      println("catalan(" + n + ") = " + catalan(n))
    }
  }
}

Catalan numbers en Python

Demostració de com implementar la seqüència de números Catalans amb Python. Vist a Rosetta code.

from math import factorial
import functools
 
def memoize(func):
    cache = {}
    def memoized(key):
        # Returned, new, memoized version of decorated function
        if key not in cache:
            cache[key] = func(key)
        return cache[key]
    return functools.update_wrapper(memoized, func)
 
 
@memoize
def fact(n):
    return factorial(n)
 
def cat_direct(n):
    return fact(2*n) // fact(n + 1) // fact(n)
 
@memoize    
def catR1(n):
    return ( 1 if n == 0
             else sum( catR1(i) * catR1(n - 1 - i)
                       for i in range(n) ) )
 
@memoize    
def catR2(n):
    return ( 1 if n == 0
             else ( ( 4 * n - 2 ) * catR2( n - 1) ) // ( n + 1 ) )
 
 
if __name__ == '__main__':
    def pr(results):
        fmt = '%-10s %-10s %-10s'
        print ((fmt % tuple(c.__name__ for c in defs)).upper())
        print (fmt % (('='*10,)*3))
        for r in zip(*results):
            print (fmt % r)
 
 
    defs = (cat_direct, catR1, catR2)
    results = [ tuple(c(i) for i in range(15)) for c in defs ]
    pr(results)

Catalan numbers en JS

Demostració de com implementar la seqüència de números Catalans amb Javascript. Vist a Rosetta code.

function disp(x) {
	var e = document.createTextNode(x + '\n');
	document.getElementById('x').appendChild(e);
}
 
var fc = [], c2 = [], c3 = [];
function fact(n) { return fc[n] ? fc[n] : fc[n] = (n ? n * fact(n - 1) : 1); }
function cata1(n) { return Math.floor(fact(2 * n) / fact(n + 1) / fact(n) + .5); }
function cata2(n) {
	if (n == 0) return 1;
	if (!c2[n]) {
		var s = 0;
		for (var i = 0; i < n; i++) s += cata2(i) * cata2(n - i - 1);
		c2[n] = s;
	}
	return c2[n];
}
function cata3(n) {
	if (n == 0) return 1;
	return c3[n] ? c3[n] : c3[n] = (4 * n - 2) * cata3(n - 1) / (n + 1);
}
 
disp("       meth1   meth2   meth3");
for (var i = 0; i <= 15; i++)
	disp(i + '\t' + cata1(i) + '\t' + cata2(i) + '\t' + cata3(i));

Catalan numbers en Java

Demostració de com implementar la seqüència de números Catalans amb Java. Vist a Rosetta code.


import java.util.HashMap;
import java.util.Map;
 
public class Catalan {
	private static final Map<Long, Double> facts = new HashMap<Long, Double>();
	private static final Map<Long, Double> catsI = new HashMap<Long, Double>();
	private static final Map<Long, Double> catsR1 = new HashMap<Long, Double>();
	private static final Map<Long, Double> catsR2 = new HashMap<Long, Double>();
 
	static{//pre-load the memoization maps with some answers 
		facts.put(0L, 1D);
		facts.put(1L, 1D);
		facts.put(2L, 2D);
 
		catsI.put(0L, 1D);
		catsR1.put(0L, 1D);
		catsR2.put(0L, 1D);
	}
 
	private static double fact(long n){
		if(facts.containsKey(n)){
			return facts.get(n);
		}
		double fact = 1;
		for(long i = 2; i <= n; i++){
			fact *= i; //could be further optimized, but it would probably be ugly
		}
		facts.put(n, fact);
		return fact;
	}
 
	private static double catI(long n){
		if(!catsI.containsKey(n)){
			catsI.put(n, fact(2 * n)/(fact(n+1)*fact(n)));
		}
		return catsI.get(n);
	}
 
	private static double catR1(long n){
		if(catsR1.containsKey(n)){
			return catsR1.get(n);
		}
		double sum = 0;
		for(int i = 0; i < n; i++){
			sum += catR1(i) * catR1(n - 1 - i);
		}
		catsR1.put(n, sum);
		return sum;
	}
 
	private static double catR2(long n){
		if(!catsR2.containsKey(n)){
			catsR2.put(n, ((2.0*(2*(n-1) + 1))/(n + 1)) * catR2(n-1));
		}
		return catsR2.get(n);
	}
 
	public static void main(String[] args){
		for(int i = 0; i <= 15; i++){
			System.out.println(catI(i));
			System.out.println(catR1(i));
			System.out.println(catR2(i));
		}
	}
}

JUnit5 Jupiter

AnnotationDescription
@TestDenotes that a method is a test method. Unlike JUnit 4’s @Test annotation
@ParameterizedTestDenotes that a method is a parameterized test.
@RepeatedTestDenotes that a method is a test template for a repeated test.
@TestFactoryDenotes that a method is a test factory for dynamic tests.
@TestTemplateDenotes that a method is a template for test cases designed to be invoked multiple times depending on the number of invocation contexts returned by the registered providers. Such methods are inherited unless they are overridden.
@TestMethodOrderUsed to configure the test method execution order for the annotated test class; similar to JUnit 4’s @FixMethodOrder. Such annotations are inherited.
@TestInstanceUsed to configure the test instance lifecycle for the annotated test class. Such annotations are inherited.
@DisplayNameDeclares a custom display name for the test class or test method.
@DisplayNameGenerationDeclares a custom display name generator for the test class.
@BeforeEachAnalogous to JUnit 4’s @Before.
@AfterEachAnalogous to JUnit 4’s @After.
@BeforeAllAnalogous to JUnit 4’s @BeforeClass.
@AfterAllAnalogous to JUnit 4’s @AfterClass.
@NestedDenotes that the annotated class is a non-static nested test class. @BeforeAll and @AfterAll methods cannot be used directly in a @Nested test class unless the “per-class” test instance lifecycle is used. Such annotations are not inherited.
@TagUsed to declare tags for filtering tests, either at the class or method level; analogous to test groups in TestNG or Categories in JUnit 4. Such annotations are inherited at the class level but not at the method level.
@DisabledAnalogous to JUnit 4’s @Ignore.
@ExtendWithUsed to register extensions declaratively. Such annotations are inherited.
@RegisterExtensionUsed to register extensions programmatically via fields. Such fields are inherited unless they are shadowed.
@TempDirUsed to supply a temporary directory via field injection or parameter injection in a lifecycle method or test method; located in the org.junit.jupiter.api.io package.
Exemples
---------------
import java.lang.reflect.Method;

import org.junit.jupiter.api.DisplayName;
import org.junit.jupiter.api.DisplayNameGeneration;
import org.junit.jupiter.api.DisplayNameGenerator;
import org.junit.jupiter.api.Nested;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

class DisplayNameGeneratorDemo {

    @Nested
    @DisplayNameGeneration(DisplayNameGenerator.ReplaceUnderscores.class)
    class A_year_is_not_supported {

        @Test
        void if_it_is_zero() {
        }

        @DisplayName("A negative value for year is not supported by the leap year computation.")
        @ParameterizedTest(name = "For example, year {0} is not supported.")
        @ValueSource(ints = { -1, -4 })
        void if_it_is_negative(int year) {
        }

    }

    @Nested
    @DisplayNameGeneration(IndicativeSentences.class)
    class A_year_is_a_leap_year {

        @Test
        void if_it_is_divisible_by_4_but_not_by_100() {
        }

        @ParameterizedTest(name = "Year {0} is a leap year.")
        @ValueSource(ints = { 2016, 2020, 2048 })
        void if_it_is_one_of_the_following_years(int year) {
        }

    }

    static class IndicativeSentences extends DisplayNameGenerator.ReplaceUnderscores {

        @Override
        public String generateDisplayNameForClass(Class<?> testClass) {
            return super.generateDisplayNameForClass(testClass);
        }

        @Override
        public String generateDisplayNameForNestedClass(Class<?> nestedClass) {
            return super.generateDisplayNameForNestedClass(nestedClass) + "...";
        }

        @Override
        public String generateDisplayNameForMethod(Class<?> testClass, Method testMethod) {
            String name = testClass.getSimpleName() + ' ' + testMethod.getName();
            return name.replace('_', ' ') + '.';
        }

    }

}
import static java.time.Duration.ofMillis;
import static java.time.Duration.ofMinutes;
import static org.junit.jupiter.api.Assertions.assertAll;
import static org.junit.jupiter.api.Assertions.assertEquals;
import static org.junit.jupiter.api.Assertions.assertNotNull;
import static org.junit.jupiter.api.Assertions.assertThrows;
import static org.junit.jupiter.api.Assertions.assertTimeout;
import static org.junit.jupiter.api.Assertions.assertTimeoutPreemptively;
import static org.junit.jupiter.api.Assertions.assertTrue;

import example.domain.Person;
import example.util.Calculator;

import org.junit.jupiter.api.Test;

class AssertionsDemo {

    private final Calculator calculator = new Calculator();

    private final Person person = new Person("Jane", "Doe");

    @Test
    void standardAssertions() {
        assertEquals(2, calculator.add(1, 1));
        assertEquals(4, calculator.multiply(2, 2),
                "The optional failure message is now the last parameter");
        assertTrue('a' < 'b', () -> "Assertion messages can be lazily evaluated -- "
                + "to avoid constructing complex messages unnecessarily.");
    }

    @Test
    void dependentAssertions() {
        // Within a code block, if an assertion fails the
        // subsequent code in the same block will be skipped.
        assertAll("properties",
            () -> {
                String firstName = person.getFirstName();
                assertNotNull(firstName);

                // Executed only if the previous assertion is valid.
                assertAll("first name",
                    () -> assertTrue(firstName.startsWith("J")),
                    () -> assertTrue(firstName.endsWith("e"))
                );
            },
            () -> {
                // Grouped assertion, so processed independently
                // of results of first name assertions.
                String lastName = person.getLastName();
                assertNotNull(lastName);

                // Executed only if the previous assertion is valid.
                assertAll("last name",
                    () -> assertTrue(lastName.startsWith("D")),
                    () -> assertTrue(lastName.endsWith("e"))
                );
            }
        );
    }

    @Test
    void timeoutNotExceededWithResult() {
        // The following assertion succeeds, and returns the supplied object.
        String actualResult = assertTimeout(ofMinutes(2), () -> {
            return "a result";
        });
        assertEquals("a result", actualResult);
    }

    @Test
    void timeoutExceeded() {
        // The following assertion fails with an error message similar to:
        // execution exceeded timeout of 10 ms by 91 ms
        assertTimeout(ofMillis(10), () -> {
            // Simulate task that takes more than 10 ms.
            Thread.sleep(100);
        });
    }


    private static String greeting() {
        return "Hello, World!";
    }
}
@Test
@EnabledOnOs({ LINUX, MAC })
void onLinuxOrMac() {
    // ...
}

@Test
@EnabledOnJre({ JAVA_9, JAVA_10 })
void onJava9Or10() {
    // ...
}

@Test
@DisabledIfEnvironmentVariable(named = "ENV", matches = ".*development.*")
void notOnDeveloperWorkstation() {
    // ...
}

@Test
@EnabledIfSystemProperty(named = "os.arch", matches = ".*64.*")
void onlyOn64BitArchitectures() {
    // ...
}

Més informació: https://junit.org/junit5/docs/current/user-guide/

AspectJ

Enllaços:

Spring Annotations
---------------

Spring AspectJ AOP implementation provides many annotations:

    @Aspect declares the class as aspect.
    @Pointcut declares the pointcut expression.

The annotations used to create advices are given below:

    @Before declares the before advice. It is applied before calling the actual method.
    @After declares the after advice. It is applied after calling the actual method and before returning result.
    @AfterReturning declares the after returning advice. It is applied after calling the actual method and before returning result. But you can get the result value in the advice.
    @Around declares the around advice. It is applied before and after calling the actual method.
    @AfterThrowing declares the throws advice. It is applied if actual method throws exception.


Exemple Spring  @AfterReturning sense @PointCut
-----------------------------------------------
  
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.annotation.AfterReturning;  
import org.aspectj.lang.annotation.Aspect;  
  
@Aspect  
public class TrackOperation{  
    @AfterReturning(  
              pointcut = "execution(* Operation.*(..))",  
              returning= "result")  
                
    public void myadvice(JoinPoint jp,Object result)//it is advice (after returning advice)  
    {  
        System.out.println("additional concern");  
        System.out.println("Method Signature: "  + jp.getSignature());  
        System.out.println("Result in advice: "+result);  
        System.out.println("end of after returning advice...");  
    }  
}  


Exemple amb @PointCut
---------------------
import org.aspectj.lang.JoinPoint;  
import org.aspectj.lang.annotation.Aspect;  
import org.aspectj.lang.annotation.After;  
import org.aspectj.lang.annotation.Pointcut;  
  
@Aspect  
public class TrackOperation{  
    @Pointcut("execution(* Operation.*(..))")  
    public void k(){}//pointcut name  
      
    @After("k()")//applying pointcut on after advice  
    public void myadvice(JoinPoint jp)//it is advice (after advice)  
    {  
        System.out.println("additional concern");  
        //System.out.println("Method Signature: "  + jp.getSignature());  
    }  
}  


Exemple @Around
-----------------
@Aspect
public class MethodLogger {
  @Around("execution(* *(..)) && @annotation(Loggable)")
  public Object around(ProceedingJoinPoint point) {
    long start = System.currentTimeMillis();
    Object result = point.proceed();
    Logger.info(
      "#%s(%s): %s in %[msec]s",
      MethodSignature.class.cast(point.getSignature()).getMethod().getName(),
      point.getArgs(),
      result,
      System.currentTimeMillis() - start
    );
    return result;
  }
}


Example Eclipse AspectJ
---------------------

aspect ColorControl {
    pointcut CCClientCflow(ColorControllingClient client):
        cflow(call(* * (..)) && target(client));   // controla quan quan es fa qualsevol crida a client

    pointcut make(): call(FigureElement Figure.make*(..));   // controla quan executa qualsevol mètode make...() de Figure.java

    after (ColorControllingClient c) returning (FigureElement fe):
            make() && CCClientCflow(c) {
        fe.setColor(c.colorFor(fe));
    }
}


aspect ContextFilling {
    pointcut parse(JavaParser jp):
        call(* JavaParser.parse*(..))
        && target(jp)
        && !call(Stmt parseVarDec(boolean)); // var decs
                                              // are tricky

    around(JavaParser jp) returns ASTObject: parse(jp) {
        Token beginToken = jp.peekToken();
        ASTObject ret = proceed(jp);
        if (ret != null) jp.addContext(ret, beginToken);
        return ret;
     }
}

Reactive Java

Reactive té a veure com optimitzar la gestió de cues de treball. Intentant minimitzar la interdependència de threads i evitar bloquejos.

Generació 0 :

Quan java feia servir callbacsk de l’estil addXXXListener(…) en Swing, AWT, Android, …

Generació 1:

Rx.NET, REactive4Java, RxJava, IObservable / IObserver. La implementació no suportava massa càrrega quan el consumidor tardava massa en processar.

Generació 2:

Per millorar l’eficiència s’inclou isUnsubscribed() i lift().

Generació 3:

Es crea l’especificació Reactive-Streams per fer compatible les implementacions.

Apareix RxJava 2, Project Reactor, Akka-Streams.

Generació 4:

S’inclou un optimitzador operator-fusion.

Apareix la implementació Fluent.

Més informació: https://akarnokd.blogspot.com/2016/03/operator-fusion-part-1.html

←Older