delete

Contattaci

back to resources

Programmazione Funzionale Java

Programmazione Funzionale
Functional Programming
Java
Software Development
data
18/1/2024
data progetto
autore
Edoardo Baral
cliente
partnership
url
No items found.

Cos’è la programmazione funzionale

Iniziamo col vedere alcuni esempi di paradigmi di programmazione.

Programmazione imperativa

Il programma viene visto come una sequenza dettagliata di istruzioni da eseguire. Lo sviluppatore deve istruire il programma su cosa deve fare e su come farlo.    

Programmazione dichiarativa

Approccio più ad alto livello rispetto alla programmazione imperativa in cui il programma viene visto come una serie di istruzioni meno dettagliate. In questo approccio, lo sviluppatore specifica cosa deve essere fatto ma non pone vincoli su come deve essere fatto.

Programmazione funzionale

Paradigma di programmazione che  vede le funzioni come elementi fondamentali, al pari degli oggetti e delle variabili. Nella programmazione funzionale, possono essere definite delle funzioni (anonime o meno) che possono essere passate come argomento ad altre funzioni oppure si possono avere funzioni che restituiscono come risultato altre funzioni.        

Programmazione orientata agli oggetti

Object Oriented Programming (OOP), basata sul concetto di oggetto, visto come entità base per la rappresentazione dell’informazione, con un suo stato e con i suoi comportamenti.

Programmazione orientata agli eventi

Paradigma che prevede che il comportamento del software sia determinato dal verificarsi di determinati eventi, più che da una sequenza precisa di istruzioni. 

Costrutti di base della programmazione funzionale

Introduciamo ora alcuni costrutti di base della programmazione funzionale, come lambda expressions, interfacce funzionali, Java Stream API e Method reference

Lambda expressions

Cosa sono? Come funzionano? Dove vengono usate?

Le lambda expressions sono costrutti introdotti in Java a partire dalla versione 8 che forniscono un modo per esprimere istanze di interfacce funzionali e scrivere codice più conciso.

Le lambda expressions generalmente prevedono:

  • lista argomenti tra parentesi tonde
  • operatore freccia/lambda
  • corpo di istruzioni tra parentesi graffe
(x, y) -> {
   istruzione1
   …
   return z;
}

La lista degli argomenti può anche essere vuota e, nel caso in cui la funzione debba solo restituire un valore, possono essere omesse l’istruzione return e le graffe. 

() -> z

Buone pratiche e consigli

  1. Usarle solo quando è necessario o conveniente, non introdurle a forza nel codice anche quando non conviene;
  2. Limitarne il più possibile la lunghezza;
  3. Evitare il passaggio come argomento di una funzione anonima espressa tramite una lambda expression se quelle stesse operazioni possono essere riutilizzate altrove nel codice;
  4. Usare dei nomi comprensibili per i parametri. 

PRO (se usate correttamente)

  • Possono migliorare la leggibilità del codice
  • Permettono la scrittura di codice più sintetico e conciso
  • Permettono la scrittura di funzioni anonime

CONTRO (se usate impropriamente)

  • Espressioni lambda troppo lunghe o complesse possono complicare significativamente la leggibilità e la manutenibilità del codice
  • L’utilizzo improprio delle lambda può portare ad una duplicazione del codice evitabile

Interfacce funzionali

Cosa sono, come funzionano, dove vengono usate e quali sono quelle più usate?

Le interfacce funzionali sono state introdotte in Java 8 per supportare la programmazione funzionale. Si definisce interfaccia funzionale un’interfaccia che contiene la dichiarazione di un unico metodo astratto.

Possono essere presenti nell’interfaccia anche altri metodi statici o di default ma è importante che ci sia un unico metodo astratto.

Grazie alle interfacce funzionali, è possibile utilizzare le espressioni lambda e la referenziazione dei metodi (method reference) in modo più efficiente.

Interfacce funzionali in Java

Consumer

L’interfaccia Consumer di Java è un’interfaccia funzionale che rappresenta un’operazione che prevede un oggetto in input ma nessun risultato in output. 

x -> {
   istruzione1
   …
   istruzioneN
}

I Consumer vengono utilizzati per compiere delle azioni sull’oggetto ricevuto in input o a partire da esso.

L’interfaccia Consumer mette a disposizione un metodo accept() che, quando chiamato, permette di svolgere le azioni contenute nel blocco di istruzioni proprie del Consumer, definite per mezzo di una lambda expression.

Supponiamo di voler definire un Consumer che, dato un numero intero, stampi il valore di questo numero e il suo quadrato. 

Consumer

Definiamo quindi una variabile Consumer<Integer> dove il generic Integer indica il tipo di oggetto trattato dal Consumer.

Il comportamento del Consumer viene descritto da una lambda expression in cui il valore in input è l’oggetto contenuto nel Consumer, un intero x, e il corpo di istruzioni contiene le azioni da far svolgere al Consumer sulla base dell’input ricevuto.

In questo caso, il Consumer stampa prima il valore intero ricevuto in input e poi il suo quadrato.

Quando il Consumer dovrà essere effettivamente utilizzato, con le operazioni in esso definite, occorrerà chiamare il metodo accept() passandogli in input l’oggetto che il Consumer dovrà utilizzare.

Esistono alcune specializzazioni dell’interfaccia Consumer che permettono di gestire implicitamente determinati tipi di oggetti.

NOME DESCRIZIONE
   
IntConsumer   

IntConsumer c = x -> {…};

Consumer che gestisce implicitamente un oggetto di tipo Integer
   
LongConsumer   

LongConsumer c = x -> {…};

Consumer che gestisce implicitamente un oggetto di tipo Long
   
DoubleConsumer   

DoubleConsumer c = x -> {…};

Consumer che gestisce implicitamente un oggetto di tipo Double

Con queste specializzazioni di Consumer, non è più necessario specificare il tipo del generic tra le parentesi angolari.

Esistono anche i BiConsumer, che sono Consumer che accettano in input due oggetti e non restituiscono nulla in output.

BiConsumer<String,Integer> bc = (s, i) -> {…};

Supplier

L’interfaccia Supplier di Java è un’interfaccia funzionale che rappresenta un’operazione che non prevede un oggetto in input ma ne restituisce uno in output.

() -> {
   istruzione1
   …
   istruzioneN
   return x;
}
 

oppure

() -> x

I Supplier vengono utilizzati per fornire un’istanza di un oggetto in output.

L’interfaccia Supplier mette a disposizione un metodo get() che, quando chiamato, permette di ottenere l’oggetto risultato del Supplier.

Come per le altre interfacce funzionali, il comportamento del Supplier è definito da una lambda expression.

Supponiamo di voler definire un Supplier che restituisca in output un numero intero casuale.

Supplier

Definiamo quindi una variabile Supplier<Integer> dove il generic Integer indica il tipo di oggetto restituito dal Supplier.

Il Supplier non necessita di oggetti in input, quindi si hanno le parentesi vuote, e restituisce in output un intero causale calcolato con il metodo Math.random() di Java.

Quando si rende necessario utilizzare realmente il Supplier, occorre chiamare il metodo get(), che restituisce il risultato della lambda expression.

Predicate

L’interfaccia Predicate di Java è un’interfaccia funzionale che permette di valutare una condizione più o meno complessa, dato un oggetto in input, e che restituisce un valore booleano in output.

x -> {
   istruzione1
   …
   istruzioneN
   return true/false;
}

oppure 

x -> true/false

I Predicate ricevono in input un oggetto e, sulla base di questo oggetto, valutano una condizione specificata nella lambda expression e restituiscono il booleano risultato di questa condizione.

L’interfaccia Predicate mette a disposizione un metodo test() che, quando chiamato, restituisce true o false a seconda che la condizione venga rispettata o meno.

Come per le altre interfacce funzionali, il comportamento del Predicate è definito da una lambda expression.

Supponiamo di voler definire un Predicate che valuti se un numero intero sia maggiore di 10. 

Predicate

Viene definita una variabile Predicate<Integer> dove il generic Integer indica il tipo di oggetto ricevuto in input e valutato nella condizione.

Il Predicate necessita di un oggetto in input e restituisce in output un booleano, risultato della condizione valutata. La lambda parte da un intero x e valuta se questo sia maggiore di 10, restituendo poi il risultato.

Quando si rende necessario utilizzare realmente il Predicate, occorre chiamare il metodo test()passandogli in input l’oggetto del Predicate, che restituisce il risultato della lambda expression.

Esistono alcune specializzazioni dell’interfaccia Predicate che permettono di gestire implicitamente determinati tipi di oggetti.

NOME DESCRIZIONE
   
IntPredicate   

IntPredicate p = x -> {…};

Predicate che gestisce implicitamente un oggetto di tipo Integer
   
LongPredicate   

LongPredicate p = x -> {…};

Predicate che gestisce implicitamente un oggetto di tipo Long
   
DoublePredicate   

DoublePredicate p = x -> {…};

Predicate che gestisce implicitamente un oggetto di tipo Double

Con queste specializzazioni di Predicate, non è più necessario specificare il tipo del generic tra le parentesi angolari.

Esistono anche i BiPredicate, che sono Predicate che accettano in input due oggetti e non restituiscono nulla in output.

BiPredicate<String,Integer> bp = (s, i) -> {…};

Function 

L’interfaccia Function di Java è un’interfaccia funzionale che permette di definire una generica funzione che, dato in input un oggetto, ne restituisca un altro in output 

x -> {
   istruzione1
   …
   istruzioneN
   return y;
}

oppure

x -> y

Le Function ricevono in input un oggetto, svolgono determinate operazioni e restituiscono in output un altro oggetto come risultato.

L’interfaccia Function mette a disposizione un metodo apply() che, quando chiamato, esegue le azioni definite dalla lambda expression e restituisce un altro oggetto in output.

Come per le altre interfacce funzionali, il comportamento delle Function è definito da una lambda expression.

Supponiamo di voler definire una Function che prenda in input una stringa e ne restituisca in output la trasformazione in caratteri minuscoli.

Function

Viene definita una variabile Function<String,String> dove il primo generic String indica il tipo di oggetto ricevuto in input e il secondo generic String indica il tipo di oggetto restituito in output.

La Function necessita di un oggetto in input e restituisce in output un altro oggetto. La lambda parte da una stringa s e restituisce la stinga convertita in caratteri minuscoli.

Quando si rende necessario utilizzare realmente la Function, occorre chiamare il metodo apply()passandogli come argomento l’oggetto da far elaborare alla Function, che restituisce il risultato della lambda expression.

Esistono alcune specializzazioni dell’interfaccia Function che permettono di gestire implicitamente determinati tipi di oggetti:

  • BiFunction
  • UnaryOperator
  • BinaryOperator

BiFunction 

Una BiFunction è una specializzazione dell’interfaccia funzionale Function che prevede in input due oggetti e restituisce un terzo oggetto in output.

BiFunction<Integer,Integer, String> bf = (x, y) -> {
   istruzione1
   ….
   istruzioneN
   return s;
}
 

UnaryOperator

L’interfaccia UnaryOperator di Java è un’interfaccia funzionale che costituisce una specializzazione di Function e permette di definire una generica funzione che, dato in input un oggetto, ne restituisca un altro in output con la particolarità che input e output devono avere lo stesso tipo (è quindi necessario specificare un solo generic al momento della dichiarazione).

UnaryOperator<Integer>unOp = x -> {
   …
   return y;
}

Similmente ad altre interfacce funzionali (es. Predicate), anche per gli UnaryOperator esistono ulteriori specializzazioni che permettono di gestire implicitamente determinati tipi di valori.

NOME DESCRIZIONE
   
IntUnaryOperator   

IntUnaryOperator op = x -> {…};

UnaryOperator che gestisce implicitamente un oggetto di tipo Integer
   
LongUnaryOperator   

LongUnaryOperator p = x -> {…};

UnaryOperator che gestisce implicitamente un oggetto di tipo Long
   
DoubleUnaryOperator   

DoubleUnaryOperator p = x -> {…};

UnaryOperator che gestisce implicitamente un oggetto di tipo Double

Supponiamo di voler definire un UnaryOperator che, dato un numero intero, ne restituisce il valore moltiplicato per 100.

UnaryOperator

L’UnaryOperator viene definito per mezzo di una lambda che, preso in input l’intero, ne restituisce il valore moltiplicato per 100.

Quando l’UnaryOperator deve essere utilizzato, occorre chiamare su di esso il metodo apply()passandogli in input un valore intero, che verrà elaborato dalla lambda.

Vediamo ora come svolgere lo stesso compito ma con le specializzazioni dell’UnaryOperator e valori numerici di tipo Integer, Long e Double.

UnaryOperator - Integer, Long e Double

BinaryOperator 

L’interfaccia BinaryOperator di Java è un’interfaccia funzionale che costituisce una specializzazione di Function e permette di definire una generica funzione che, dato in input due oggetti, ne restituisca un terzo in output con la particolarità che input e output devono avere lo stesso tipo (è quindi necessario specificare un solo generic al momento della dichiarazione).

BinaryOperator<Integer>biOp = (x, y) -> {
   …
   return z;
}

Similmente agli UnaryOperator, anche per i BinaryOperator esistono ulteriori specializzazioni che permettono di gestire implicitamente determinati tipi di valori.

NOME DESCRIZIONE
   
IntBinaryOperator   

IntBinaryOperator op = (x, y) -> {…};

BinaryOperator che gestisce implicitamente un oggetto di tipo Integer
   
LongBinaryOperator   

LongBinaryOperator op = (x, y) -> {…};

UnaryOperator che gestisce implicitamente un oggetto di tipo Long
   
DoubleBinaryOperator   

DoubleBinaryOperator op = (x, y) -> {…};

BinaryOperator che gestisce implicitamente un oggetto di tipo Double

Supponiamo di voler definire un BinaryOperator che, dati due numeri interi, ne restituisca la somma.

BinaryOperator

Il BinaryOperator viene definito per mezzo di una lambda che, presi in input due interi, ne restituisce la somma (anch’essa di tipo intero).

Quando il BinaryOperator deve essere utilizzato, occorre chiamare su di esso il metodo apply()passandogli in input due valori interi, che verranno elaborati dalla lambda. 

Vediamo ora come svolgere lo stesso compito ma con le specializzazioni del BinaryOperator e valori numerici di tipo Integer, Long e Double.

BinaryOperator - Integer, Long e Double

Java Stream API

Cosa sono, come funzionano, dove vengono usate e quali sono i metodi più usati?

Le Java Stream API sono delle funzioni introdotte a partire dalla versione 8 di Java che mettono a disposizione una serie di operazioni ad alto livello per eseguire operazioni divario tipo su aggregazioni di dati (es. collections) tra cui:

  • ciclo
  • filtraggio
  • trasformazione
  • aggregazione

A partire da un’aggregazione di elementi (come può essere ad esempio una Map, una List, un Set o una qualunque altra forma di istanza di Collection), si apre uno Stream, ovvero un oggetto che rappresenta la sequenza di elementi presenti nella Collection su cui vogliamo operare.

Su questo Stream è poi possibile chiamare i metodi delle Java Stream API per leggere, modificare o elaborare gli elementi del suddetto Stream.

Java Stream API
METODO DESCRIZIONE
   
stream()   

Crea uno Stream a partire da una Collection
   
forEach()   

Permette di ciclare su ogni elemento presente nello Stream
   
filter()   

Restituisce uno Stream contenente gli elementi dello Stream di partenza che soddisfano una certa condizione
   
collect()   

Permette di trasformare uno Stream in una particolare implementazione di Collection (es. ArrayList)
   
map()   

Permette di trasformare il tipo di oggetti trattati dallo Stream
   
flatMap()   

Come map() ma usato in caso di oggetti complessi
   
count()   

Restituisce il numero di elementi di uno Stream
   
distinct()   

Rimuove gli elementi duplicati in uno Stream
   
sorted()   

Ordina gli elementi di uno Stream in base ad un criterio specificato
   
anyMatch()   

Restituisce true se è presente nello Stream almeno un elemento che rispetta una data condizione, false altrimenti
   
allMatch()   

Restituisce true se tutti gli elementi dello Stream rispettano una data condizione, false altrimenti
   
noneMatch()   

Restituisce true se nessuno degli elementi dello Stream rispetta una data condizione, false altrimenti

ForEach

Il metodo forEach() delle Java Stream API permette di eseguire una determinata azione su ogni elemento di uno Stream.

Il metodo richiede come argomento un Consumer che descriva le azioni da svolgere su ogni elemento dello Stream.

Ad esempio, supponiamo di voler ciclare su una Collection di stringhe e di voler stampare ogni elemento.

ForEach

Dopo aver definito una lista di stringhe, si apre uno Stream su di essa usando il metodo stream() dopodiché si chiama sullo Stream il metodo forEach() passandogli come argomento un Consumer definito per mezzo di una lambda.

Tale lambda, dato ogni elemento s dello Stream, lo stampa.

Filter e collect

Il metodo filter() delle Java Stream API, dato uno Stream di partenza, permette di generare un secondo Stream contenente tutti gli elementi del primo che soddisfano una certa condizione, espressa per mezzo di un Predicate.

Ad esempio, supponiamo di voler estrarre da una lista di stringhe solo quelle che iniziano con la lettera M.

Filter

Dopo aver aperto uno Stream su una lista di stringhe, si chiama il metodo filter() su di esso passandogli come argomento un Predicate con la condizione da verificare, espressa in questo caso per mezzo di una lambda.

Per convertire lo Stream restituito da filter() in una List, si può usare si di esso il metodo collect(Collectors.toList())

Map

Il metodo map() delle Java Stream API permette di applicare una Function ad ogni elemento di uno Stream. Tale funzione viene usata per generare un secondo Stream contenente gli elementi risultanti dall’applicazione di tale funzione. 

Ad esempio, supponiamo di avere a disposizione una lista di stringhe e di voler ottenere una lista di interi che indicano la lunghezza di ogni elemento dello Stream di partenza.

Map

Dopo aver aperto uno Stream su una lista di stringhe, si chiama il metodo map() su di esso passandogli una Function come argomento, espressa in questo caso per mezzo di una lambda.

Tale Function prende in input ogni stringa dello Stream di partenza e restituisce in output la sua lunghezza. Lo Stream risultante sarà quindi uno Stream di Integer.

Si vede quindi che map() può essere utilizzato anche per «trasformare» il tipo di oggetti che sista considerando nello Stream. 

Count e distinct 

Il metodo count() delle Java Stream API restituisce il numero di elementi presenti in uno Stream.

Il metodo distinct(), invece, dato uno Stream, permette di generarne un secondo contenente gli elementi del primo senza duplicati.

Ad esempio, supponiamo di avere a disposizione una lista di stringhe con duplicati e di volerne contare gli elementi dopo aver rimosso i duplicati.

Count e distinct

Dopo aver aperto uno Stream su una lista di stringhe, si chiama il metodo distinct() su di esso per generare un secondo Stream contenente i soli elementi del primo senza duplicati.

Dopodiché si chiama count() che restituisce il numero di elementi dello Stream generato da distinct() sotto forma di valore numerico long.

Sorted

Il metodo sorted() delle Java Stream API permette di generare uno Stream contenente gli elementi dello Stream su cui è stato chiamato ordinati secondo uno specifico criterio. 

Ad esempio, supponiamo di avere a disposizione una lista di stringhe e di volerla ordinare in ordine alfabetico inverso.

Sorted

Dopo aver aperto uno Stream su una lista di stringhe, si chiama il metodo sorted() su di esso. passandogli come argomento un Comparator che indichi il tipo di ordinamento che va applicato agli elementi dello Stream.

In caso non venga specificato alcun Comparator, sorted() applicherà l’ordinamento naturale sulla base del tipo di elementi trattati, posto che ve ne sia uno. 

Si ottiene quindi un secondoStream contenente gli elementi di quello di partenza ordinati secondo il criterio stabilito.

AnyMatch, allMatch e noneMatch 

Il metodo anyMatch() delle Java Stream API permette di verificare che almeno un elemento delloStream soddisfi una certa condizione, espressa per mezzo di un Predicate.

Analogamente allMatch() verifica se tutti gli elementi dello Stream soddisfano una certa condizione, mentre noneMatch() verifica che nessuno di essi la soddisfi.

Ad esempio, supponiamo di avere a disposizione una lista di stringhe e di voler verificare se almeno una di esse inizi per M, se tutte iniziano per M e se nessuna di esse inizia per M.

anyMatch, allMatch, noneMatch

Dopo aver aperto uno Stream su una lista di stringhe, si chiama il metodo anyMatch() su di esso passandogli come argomento un Predicate che descriva la condizione da verificare. Il metodo restituisce true/false a seconda dell’esito della valutazione.

Essendoci nello Stream almeno una stringa che inizia per M, il metodo restituisce true.

I metodi allMatch() e noneMatch() funzionano in modo del tutto analogo.

Method reference

Con method reference si indica una funzionalità di Java introdotta a partire dalla versione 8 che consente di riferirsi ai metodi (in determinate circostanze) con una sintassi più concisa rispetto anche alle espressioni lambda.

Il method reference viene spesso utilizzato congiuntamente alle Java Stream API in sostituzione delle espressioni lambda.

Supponiamo di avere una lista di stringhe e di volerle stampare tutte.

Usando le Java Stream API e le espressioni lambda, potremmo scrivere un frammento di codice del genere.

Sfruttando il method reference, potremmo rendere la scrittura ancora più sintetica e concisa, facendo riferimento al metodo statico println().

Si indica quindi il nome della classe seguito dall’operatore di riferimento :: e dal nome del metodo da utilizzare, senza specificare gli argomenti.

NomeClasse::nomeMetodo

Il risultato può essere interpretato in questo modo:

  1. Si apre uno Stream sulla lista di stringhe.
  2. Si chiama il metodo forEach() che, come spiegato in precedenza, vuole come argomento un Consumer.
  3. Per ogni stringa s dello Stream, si chiama System.out.println() passandogli come argomento proprio s.

Occorre notare che, usando il method reference al posto di una lambda, bisogna fare in modo che il tipo di input della lambda sia compatibile in numero e tipo con la lista dei parametri del metodo referenziato.

In altre parole, sfruttando sempre l’esempio precedente, abbiamo che la lambda prende in input una stringa s data da forEach(), che è compatibile con la lista dei parametri che è possibile passare a System.out.println().

Pertanto la lambda è sostituibile con il method reference.

Risultati

resources

User Experience Design tra accessibilità e inclusività

User Experience Design tra accessibilità e inclusività

User Experience

Accessibilità

Inclusività

Assitech.Net entra nella galassia Orbyta Technologies

Assitech.Net entra nella galassia Orbyta Technologies

Orbyta Technologies

Orbyta Group

Acquisizione

News

Programmazione Funzionale Java

Programmazione Funzionale Java

Functional Programming

Java

Software Development

Reactive Programming: parallelizzare con Project Reactor

Reactive Programming: parallelizzare con Project Reactor

Programmazione Reattiva

Reactive Programming

Project Reactor

Piattaforme E-commerce Wholesale per il settore B2B

Piattaforme E-commerce Wholesale per il settore B2B

Wholesale

B2B

Antipattern nello sviluppo software: altri errori da evitare

Antipattern nello sviluppo software: altri errori da evitare

Software Development

Antipattern nello sviluppo software: definizione, ambiti di applicazione ed esempi

Antipattern nello sviluppo software: definizione, ambiti di applicazione ed esempi

Software Development

App tattiche di supporto alla gestione dei progetti reiterativi

App tattiche di supporto alla gestione dei progetti reiterativi

App Development

Power Platform

Low Code

DevOps

Introduzione a Power Pages, il servizio Microsoft per siti web low-code

Introduzione a Power Pages, il servizio Microsoft per siti web low-code

Microsoft

Low-code

Power Platform

Introduzione a Jupyter e Seaborn per Data Analysis e Visualization

Introduzione a Jupyter e Seaborn per Data Analysis e Visualization

Jupiter

Python

Data Analysis

Data Visualization

Come utilizzare Matplotlib per la Data Visualization in Python

Come utilizzare Matplotlib per la Data Visualization in Python

Python

Data Visualization

Data Science

Data Analysis

Introduzione alla libreria Dash per Python

Introduzione alla libreria Dash per Python

Python

Data Science

Data Visualization

Data Analysis

Prime Video passa al monolite: ma allora serverless è inutile? 

Prime Video passa al monolite: ma allora serverless è inutile? 

Tableau per la Business Intelligence: introduzione, tutorial e confronto

Tableau per la Business Intelligence: introduzione, tutorial e confronto

Introduzione a Qlik Sense, piattaforma di Business Intelligence avanzata

Introduzione a Qlik Sense, piattaforma di Business Intelligence avanzata

Applicazioni Cloud Native: definizione, vantaggi e tecnologie

Applicazioni Cloud Native: definizione, vantaggi e tecnologie

Power Apps Tutorial – Case Study: come costruire una business app da zero

Power Apps Tutorial – Case Study: come costruire una business app da zero

Il futuro del gaming tra F2P, GaaS, Crypto e Play to Earn

Il futuro del gaming tra F2P, GaaS, Crypto e Play to Earn

Power Apps Basics: interfacce, implementazione & vantaggi

Power Apps Basics: interfacce, implementazione & vantaggi

Strumenti di Business Intelligence: QlikSense & Power BI a confronto

Strumenti di Business Intelligence: QlikSense & Power BI a confronto

Introduzione a Serverless: non solo Lambda Function

Introduzione a Serverless: non solo Lambda Function

Metaverso: siamo pronti a cogliere l’opportunità?

Metaverso: siamo pronti a cogliere l’opportunità?

Recap Flutter Forward 2023: le 7 novità più interessanti

Recap Flutter Forward 2023: le 7 novità più interessanti

Let's Redux React to a Game

Let's Redux React to a Game

Introduzione a PowerShell

Introduzione a PowerShell

Pago con carta: i trend dei pagamenti digitali e il futuro delle carte di credito

Pago con carta: i trend dei pagamenti digitali e il futuro delle carte di credito

NFT World: il fenomeno NFT tra metaverso, business e GameFi

NFT World: il fenomeno NFT tra metaverso, business e GameFi

Quick Escape Room

Quick Escape Room

Orbyta Invaders Ignition

Orbyta Invaders Ignition

Il lancio della nuova Identity di Orbyta parte dal Metaverso!

Il lancio della nuova Identity di Orbyta parte dal Metaverso!

development

design

metaverse

brand identity

Database a grafo in SQL Server

Database a grafo in SQL Server

Data Science Job Roles: i 4 ruoli più richiesti nel settore

Data Science Job Roles: i 4 ruoli più richiesti nel settore

Teoria dei giochi: Propagazione delle strategie

Teoria dei giochi: Propagazione delle strategie

The chosen one: .NET 5

The chosen one: .NET 5

Network Science e Social Network Analysis

Network Science e Social Network Analysis

Isolation levels on SSMS

Isolation levels on SSMS

Teoria dei Grafi

Teoria dei Grafi

Creare un podcast in automatico a partire da audio vocali e musica

Creare un podcast in automatico a partire da audio vocali e musica

Teoria dei Giochi

Teoria dei Giochi

Recommender systems: principali metodologie degli algoritmi di suggerimento

Recommender systems: principali metodologie degli algoritmi di suggerimento

Introduction to Quantum Computing and Qiskit

Introduction to Quantum Computing and Qiskit

System Versioned Tables

System Versioned Tables

Vim o non Vim

Vim o non Vim

I vantaggi di un Message Broker

I vantaggi di un Message Broker

PlayStation 5 e l'accesso ai dati: un cambio architetturale?

PlayStation 5 e l'accesso ai dati: un cambio architetturale?

Protezione dei Web Services

Protezione dei Web Services

need more info?

contattaci