En aquest tema, explorarem dos conceptes fonamentals en la programació funcional: els Functors i les Monads. Aquests conceptes són essencials per treballar amb estructures de dades i efectes de manera funcional i composable.

Introducció

Què és un Functor?

Un Functor és una estructura que pot ser mappejada. En altres paraules, és una estructura que implementa una operació map que aplica una funció a cada element dins de la seva estructura.

Què és una Monad?

Una Monad és una estructura que representa càlculs seqüencials. Les Monads proporcionen una manera de seqüenciar operacions, encapsulant valors i efectes de manera que es puguin composar fàcilment.

Functors

Definició de Functor

Un Functor és qualsevol tipus que implementa la interfície map. En Scala, això es pot representar amb la següent signatura:

trait Functor[F[_]] {
  def map[A, B](fa: F[A])(f: A => B): F[B]
}

Exemple de Functor: Option

L'objecte Option és un exemple clàssic de Functor. Vegem com funciona:

val someValue: Option[Int] = Some(10)
val noneValue: Option[Int] = None

val incrementedSome: Option[Int] = someValue.map(_ + 1) // Some(11)
val incrementedNone: Option[Int] = noneValue.map(_ + 1) // None

Exercici Pràctic: Implementar un Functor

Implementa un Functor per a una llista personalitzada:

sealed trait MyList[+A]
case object MyNil extends MyList[Nothing]
case class MyCons[+A](head: A, tail: MyList[A]) extends MyList[A]

object MyList {
  def map[A, B](list: MyList[A])(f: A => B): MyList[B] = list match {
    case MyNil => MyNil
    case MyCons(head, tail) => MyCons(f(head), map(tail)(f))
  }
}

Monads

Definició de Monad

Una Monad és qualsevol tipus que implementa les operacions flatMap i unit (també coneguda com pure o return). En Scala, això es pot representar amb la següent signatura:

trait Monad[M[_]] {
  def flatMap[A, B](ma: M[A])(f: A => M[B]): M[B]
  def unit[A](a: A): M[A]
}

Exemple de Monad: Option

L'objecte Option també és un exemple de Monad. Vegem com funciona:

val someValue: Option[Int] = Some(10)
val noneValue: Option[Int] = None

val resultSome: Option[Int] = someValue.flatMap(x => Some(x + 1)) // Some(11)
val resultNone: Option[Int] = noneValue.flatMap(x => Some(x + 1)) // None

Exercici Pràctic: Implementar una Monad

Implementa una Monad per a una llista personalitzada:

sealed trait MyList[+A]
case object MyNil extends MyList[Nothing]
case class MyCons[+A](head: A, tail: MyList[A]) extends MyList[A]

object MyList {
  def flatMap[A, B](list: MyList[A])(f: A => MyList[B]): MyList[B] = list match {
    case MyNil => MyNil
    case MyCons(head, tail) => concat(f(head), flatMap(tail)(f))
  }

  def unit[A](a: A): MyList[A] = MyCons(a, MyNil)

  def concat[A](list1: MyList[A], list2: MyList[A]): MyList[A] = list1 match {
    case MyNil => list2
    case MyCons(head, tail) => MyCons(head, concat(tail, list2))
  }
}

Comparació entre Functors i Monads

Característica Functor Monad
Operació clau map flatMap i unit
Propòsit Transformar elements dins d'una estructura Seqüenciar operacions i encapsular efectes

Exercicis Pràctics

  1. Implementar un Functor per a Either:

    sealed trait MyEither[+E, +A]
    case class MyLeft[+E](value: E) extends MyEither[E, Nothing]
    case class MyRight[+A](value: A) extends MyEither[Nothing, A]
    
    object MyEither {
      def map[E, A, B](either: MyEither[E, A])(f: A => B): MyEither[E, B] = either match {
        case MyLeft(e) => MyLeft(e)
        case MyRight(a) => MyRight(f(a))
      }
    }
    
  2. Implementar una Monad per a Either:

    object MyEither {
      def flatMap[E, A, B](either: MyEither[E, A])(f: A => MyEither[E, B]): MyEither[E, B] = either match {
        case MyLeft(e) => MyLeft(e)
        case MyRight(a) => f(a)
      }
    
      def unit[E, A](a: A): MyEither[E, A] = MyRight(a)
    }
    

Resum

En aquest tema, hem après sobre dos conceptes fonamentals en la programació funcional: els Functors i les Monads. Hem vist com aquests conceptes ens permeten treballar amb estructures de dades i efectes de manera composable i funcional. Hem explorat exemples pràctics amb Option i hem implementat Functors i Monads per a estructures personalitzades.

En el proper tema, explorarem les For-Comprehensions, una característica poderosa de Scala que facilita el treball amb Monads.

© Copyright 2024. Tots els drets reservats