0Pricing
Scala for Backend Engineering & Functional Programming · Lezione

Type class e impliciti

Impari a utilizzare le type class per il polimorfismo ad hoc e a sfruttare il sistema degli impliciti di Scala per creare astrazioni potenti.

Type class e impliciti è una lezione Scala for Backend Engineering & Functional Programming gratuita su CoddyKit. Questa è la lezione 3 di 3. Puoi leggere la lezione completa qui gratuitamente — poi esercitati direttamente nel browser con un editor di codice integrato e un tutor IA disponibile 24/7. Fa parte del percorso di apprendimento Scala for Backend Engineering & Functional Programming, e i tuoi progressi si sincronizzano tra il web e l'app CoddyKit. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.

Il polimorfismo ad hoc spiegato

In Scala, il polimorfismo consiste nello scrivere codice che funziona con tipi diversi. Ha già visto il polimorfismo per sottotipizzazione con l'ereditarietà, in cui un metodo funziona per una classe base e per tutte le sue sottoclassi.

Il polimorfismo ad hoc è diverso. Consente a una singola funzione di comportarsi in modo diverso in base al tipo specifico ricevuto, anche se questi tipi non sono correlati da ereditarietà. È qui che brillano le Type Classes!

Che cosa sono le Type Classes?

Una Type Class è un pattern di progettazione che consente di aggiungere nuovi comportamenti a tipi esistenti senza modificarli e senza usare l'ereditarietà.

  • Definisce un contratto (un trait) per un comportamento specifico.
  • Fornisce delle "istanze" (oggetti) che implementano questo contratto per tipi diversi.
  • Usa il meccanismo degli impliciti di Scala per rendere automaticamente disponibile nell'ambito il comportamento corretto.

Definizione di un trait per una Type Class

Per prima cosa, definiamo un trait che descrive il comportamento desiderato. Questo trait accetta un parametro di tipo, A, che rappresenta il tipo su cui verrà applicato il comportamento.

Creiamo una type class Printable che sappia convertire qualsiasi tipo in una stringa leggibile.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

Creazione delle istanze della Type Class

Ora dobbiamo dire a Scala come rendere specifici tipi Printable. Per farlo, creiamo degli oggetti impliciti (o dei valori) che estendono il nostro trait Printable per ciascun tipo.

Questi vengono chiamati istanze della type class.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

Utilizzo delle Type Class con gli impliciti

Per usare la nostra type class, definiamo una funzione che accetta un parametro implicito del tipo della type class. Quando la funzione viene chiamata, il compilatore di Scala cercherà automaticamente un'istanza implicita disponibile di Printable[A].

Se ne trova una, la inserisce nella funzione!

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

object PrintableSyntax {
  def print[A](value: A)(implicit p: Printable[A]): Unit = {
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    print("hello")
    print(123)
  }
}

Context bound: una sintassi più semplice

Scala offre una forma abbreviata, chiamata context bound, per i parametri impliciti. Invece di (implicit p: Printable[A]), è possibile scrivere [A: Printable].

Questo rende il codice più chiaro, soprattutto quando sono presenti più parametri impliciti.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }
}

object PrintableSyntax {
  // Using context bound: [A: Printable] is shorthand for (implicit p: Printable[A])
  def print[A: Printable](value: A): Unit = {
    // To access the implicit instance, use implicitly[Printable[A]]
    val p = implicitly[Printable[A]]
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    print("world")
    print(456)
  }
}

Estensione ai tipi personalizzati

Il vantaggio delle type class è che consentono di aggiungere nuovi comportamenti a qualsiasi tipo, anche a tipi che non appartengono al proprio codice, come Int o String, oppure a tipi personalizzati come le case class.

Rendiamo Person Printable.

package com.coddykit

trait Printable[A] {
  def format(value: A): String
}

case class Person(name: String, age: Int)

object PrintableInstances {
  implicit object StringPrintable extends Printable[String] {
    def format(value: String): String = s"'${value}'"
  }

  implicit object IntPrintable extends Printable[Int] {
    def format(value: Int): String = s"${value}i"
  }

  // New instance for our custom Person type
  implicit object PersonPrintable extends Printable[Person] {
    def format(person: Person): String = 
      s"Person(name: ${person.name}, age: ${person.age})"
  }
}

object PrintableSyntax {
  def print[A: Printable](value: A): Unit = {
    val p = implicitly[Printable[A]]
    println(p.format(value))
  }
}

object Main {
  import PrintableInstances._
  import PrintableSyntax._

  def main(args: Array[String]): Unit = {
    val alice = Person("Alice", 30)
    print(alice)
  }
}

Impliciti: non solo Type Class

Sebbene le type class siano un caso d'uso principale, la parola chiave implicit in Scala è un meccanismo più generale. Può essere usata per:

  • Parametri impliciti: come nel caso delle type class, per fornire automaticamente dei valori.
  • Conversioni implicite: per convertire automaticamente un tipo in un altro (da usare con cautela, perché possono rendere il codice più difficile da seguire).
  • Valori impliciti: per fornire valori predefiniti per determinati tipi, spesso usati per elementi come i contesti di esecuzione.

L'idea fondamentale è che il compilatore cerca nell'ambito definizioni implicite adatte.

Vantaggi delle Type Class

Le type class offrono diversi vantaggi:

  • Estensibilità: aggiungere nuovi comportamenti a tipi esistenti senza modificarli.
  • Disaccoppiamento: separare la definizione del tipo dal suo comportamento.
  • Polimorfismo ad hoc: le funzioni possono lavorare con qualsiasi tipo che fornisca il comportamento richiesto.
  • Testabilità: rendere più semplici i test dei comportamenti in isolamento.
  • Assenza dei costi dell'ereditarietà: evitare le complessità delle gerarchie di ereditarietà profonde.

Verifica rapida

Consideri la type class Printable e le relative istanze presentate nella lezione. Se si chiama print(10.5), supponendo che non esista alcuna istanza DoublePrintable, che cosa succede?

Riepilogo e prossimi passi

Ha acquisito padronanza delle Type Class e del potente sistema degli impliciti di Scala!

  • Le Type Class abilitano il polimorfismo ad hoc, consentendo di definire comportamenti per i tipi senza usare l'ereditarietà.
  • Sono costituite da un trait (il contratto) e da oggetti/valori impliciti (le istanze).
  • I parametri impliciti e i context bound vengono usati dalle funzioni per trovare e utilizzare automaticamente queste istanze.

Le type class sono un elemento fondamentale della programmazione funzionale in Scala e vengono usate ampiamente in librerie come Cats e ZIO per creare applicazioni flessibili e robuste. Continui a esercitarsi per consolidare la sua comprensione!

Domande Frequenti

La lezione «Type class e impliciti» è gratuita?

Sì — il testo completo di «Type class e impliciti» è gratuito qui sul web. Per esercitarvi in modo interattivo (un editor di codice integrato e un tutor IA 24/7) e sbloccare il resto del corso Scala for Backend Engineering & Functional Programming, passa a CoddyKit PRO. Il corso Scala for Backend Engineering & Functional Programming include 3 lezioni in totale.

Cosa imparerò in «Type class e impliciti»?

Impari a utilizzare le type class per il polimorfismo ad hoc e a sfruttare il sistema degli impliciti di Scala per creare astrazioni potenti. Eserciti Scala for Backend Engineering & Functional Programming con codice pratico che esegui direttamente nel browser, e un tutor IA 24/7 risponde alle tue domande mentre lavori sulla lezione.

Ho bisogno di esperienza per iniziare Scala for Backend Engineering & Functional Programming?

Non è richiesta alcuna esperienza precedente. Scala for Backend Engineering & Functional Programming su CoddyKit è strutturato per principianti e studenti avanzati, quindi puoi iniziare da qui o dall'inizio e procedere al tuo ritmo. Questa è la lezione 3 di 3.

Quanto tempo richiede la lezione «Type class e impliciti»?

La maggior parte delle lezioni CoddyKit richiede circa 5–10 minuti. Ogni lezione è breve e interattiva, quindi fai progressi costanti e riprendi esattamente da dove hai lasciato su web e app.

Posso scrivere ed eseguire codice in questa lezione Scala for Backend Engineering & Functional Programming?

Sì. Ogni lezione Scala for Backend Engineering & Functional Programming include un editor di codice integrato, quindi scrivi ed esegui codice reale direttamente nel tuo browser e ricevi feedback istantaneo dall'IA — nessuna configurazione locale necessaria.

Tutte le lezioni di questo corso

  1. Generics e parametri di tipo
  2. Varianza: covarianza e controvarianza
  3. Type class e impliciti
← Torna a Scala for Backend Engineering & Functional Programming