tutorial programming learn koans functional exercises codecademy scala functional-programming

learn - scala programming



Scala Functor y Monad diferencias (5)

¿Puede alguien explicar las diferencias entre Functor y Monad en el contexto de Scala?



El mejor artículo que presenta en detalle esas dos nociones es " La esencia del patrón iterador " del blog de Eric Torreborre .

Functor

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

  • Una forma de interpretar un Functor es describirlo como un cálculo de valores de tipo A
    Por ejemplo:
    • List[A] es un cálculo que devuelve varios valores de tipo A (cálculo no determinista),
    • Option[A] es para cálculos que puede o no puede tener,
    • Future[A] es un cálculo de un valor de tipo A que obtendrá más adelante, y así sucesivamente.
  • Otra forma de representarlo es como una especie de "contenedor" para valores de tipo A.

Es la capa básica a partir de la cual defines:

  • PointedFunctor (para crear un valor de tipo F[A] ) y
  • Applic (para proporcionar un método de applic , que es un valor calculado dentro del contenedor F (F[A => B]) , para aplicarlo a un valor F[A] ), Applicative Functor (agregación de un Applic y un PointedFunctor ).

Los tres elementos se usan para definir una Monad .


Hace un tiempo escribí sobre eso: gabrielsw.blogspot.com/2011/08/… (aunque no soy un experto)

Lo primero que debes entender es el tipo ''T [X]'': es una especie de "contexto" (es útil para codificar cosas en tipos y con esto las estás "componiendo") Pero mira las otras respuestas :)

Ok, ahora tienes tus tipos dentro de un contexto, digamos M [A] (A "dentro" M), y tienes una función simple f: A => B ... no puedes seguir adelante y aplicarla, porque la función espera A y tienes M [A]. Necesita alguna forma de "descomprimir" el contenido de M, aplicar la función y "empacar" nuevamente. Si tienes un conocimiento "íntimo" de las partes internas de M, puedes hacerlo, si lo generalizas en un rasgo que terminas con

trait Functor[T[_]]{ def fmap[A,B](f:A=>B)(ta:T[A]):T[B] }

Y eso es exactamente lo que es un functor. Transforma un T [A] en un T [B] aplicando la función f.

Una mónada es una criatura mítica con una comprensión elusiva y múltiples metáforas, pero me resulta bastante fácil de entender una vez que obtienes el functor aplicativo:

Functor nos permite aplicar funciones a cosas en un contexto. Pero, ¿y si las funciones que queremos aplicar ya están en un contexto? (Y es bastante fácil terminar en esa situación si tiene funciones que toman más de un parámetro).

Ahora necesitamos algo así como un Functor, pero eso también toma funciones que ya están en el contexto y las aplica a los elementos en el contexto. Y eso es lo que es el functor aplicativo. Aquí está la firma:

trait Applicative[T[_]] extends Functor[T]{ def pure[A](a:A):T[A] def <*>[A,B](tf:T[A=>B])(ta:T[A]):T[B] }

Hasta aquí todo bien. Ahora vienen las mónadas: ¿y si ahora tienes una función que pone las cosas en el contexto? Su firma será g: X => M [X] ... no puedes usar un funtor porque espera X => Y, así que terminaremos con M [M [X]], no puedes usar el functor aplicativo porque espera la función ya en el contexto M [X => Y].

Entonces usamos una mónada, que toma una función X => M [X] y algo que ya está en el contexto M [A] y aplica la función a lo que está dentro del contexto, empaquetando el resultado en un solo contexto. La firma es:

trait Monad[M[_]] extends Applicative[M]{ def >>=[A,B](ma:M[A])(f:A=>M[B]):M[B] }

Puede ser bastante abstracto, pero si piensas en cómo trabajar con "Opción", te muestra cómo componer las funciones X => Opción [X]

EDITAR: Olvidé lo importante para atarlo: el símbolo >> = se llama bind y es flatMap en Scala. (Además, como nota al margen, hay algunas leyes que los funtores, los solicitantes y las mónadas deben seguir para funcionar correctamente).


Tomando Scalaz como punto de referencia, un tipo F[_] (es decir, un tipo F que se parametriza con un solo tipo) es un funtor si se puede levantar una función en él. Qué significa esto:

class Function1W[A, B](self: A => B) { def lift[F[_]: Functor]: F[A] => F[B] }

Es decir, si tengo una función A => B , un functor F[_] , entonces ahora tengo una función F[A] => F[B] . Esto es realmente lo contrario de mirar el método de map CanBuildFrom , que (ignorando las cosas de CanBuildFrom ) es básicamente:

F[A] => (A => B) => F[B]

Si tengo una List of Strings, una función de String a Int, entonces obviamente puedo producir una lista de Ints. Esto se aplica a Option, Stream, etc. Todos son funtores

Lo que me parece interesante acerca de esto es que puedes saltar inmediatamente a la conclusión (incorrecta) de que un Functor es un "contenedor" de A s. Esta es una restricción innecesaria. Por ejemplo, piense en una función X => A Si tengo una función X => A y una función A => B entonces claramente, por composición, tengo una función X => B Pero ahora, míralo de esta manera:

type F[Y] = X => Y //F is fixed in X (X => A) andThen (A => B) is X => B F[A] A => B F[B]

Entonces el tipo X => A para alguna X fija también es un funtor. En scalaz , functor está diseñado como un rasgo de la siguiente manera:

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

por lo tanto, se implementa el método Function1.lift anterior

def lift[F[_]: Functor]: F[A] => F[B] = (f: F[A]) => implicitly[Functor[F]].fmap(f, self)

Un par de instancias de functor:

implicit val OptionFunctor = new Functor[Option] { def fmap[A, B](fa: Option[A], f: A => B) = fa map f } implicit def Functor1Functor[X] = new Functor[({type l[a]=X => a})#l] { def fmap[A, B](fa: X => B, f: A => B) = f compose fa }

En scalaz , una mónada está diseñada así:

trait Monad[M[_]] { def pure[A](a: A): M[A] //given a value, you can lift it into the monad def bind[A, B](ma: M[A], f: A => B): M[B] }

No es particularmente obvio cuál es la utilidad de esto. Resulta que la respuesta es "muy". Descubrí que las Mónadas de Daniel Spiewak no son muy claras al describir por qué esto podría ser y también las cosas de Tony Morris sobre la configuración a través de la mónada del lector , un buen ejemplo práctico de lo que podría significar escribir tu programa dentro de una mónada .


Scala en sí mismo no enfatiza tanto los términos Functor y Monad . Supongo que usar map es el lado del functor, usar flatMap es el lado de la mónada.

Para mí, mirar y jugar con scalaz ha sido hasta ahora la mejor manera de tener una idea de esos conceptos funcionales en el contexto scala (versus el contexto haskell). Hace dos años, cuando comencé Scalaz, el código scalaz era un galimatías para mí, luego, hace unos meses, comencé a buscar de nuevo y me di cuenta de que realmente es una implementación limpia de ese estilo particular de programación funcional.

Por ejemplo, la implementación de Monad muestra que una mónada es un functor puntiagudo porque amplía el rasgo Pointed (así como el rasgo Applicative ). Te invito a ir a ver el código. Tiene enlaces en la fuente misma y es muy fácil seguir los enlaces.

Entonces los funtores son más generales. Las mónadas proporcionan funciones adicionales. Para tener una idea de lo que puedes hacer cuando tienes un functor o cuando tienes una mónada, puedes mirar a MA

Verá los métodos de utilidad que necesitan un functor implícito (en particular, funtores aplicativos), como los métodos de sequence y algún tiempo que necesitan una mónada completa, como replicateM .