Functor función en Haskell
⏱ Dedicación recomendada: 0 minutos
Esto considera el contenido visible y relevante, e ignora texto colapsado o marcado como opcional.
r8vnhill/
Ya viste en Kotlin cómo una función (T) -> R
puede comportarse como un funtor al permitir transformar su salida sin alterar su entrada. Esta idea se implementaba mediante una función de extensión map
, lo que te permitió encadenar transformaciones de forma declarativa sobre el resultado de una función.
En Haskell, este concepto se formaliza aún más: el tipo (->) r
(funciones que reciben un valor de tipo r
) es una instancia nativa del tipo de clase Functor
. Es decir, todas las funciones que comparten el mismo tipo de entrada son automáticamente funtores en su salida.
Esto implica que puedes usar fmap
directamente sobre funciones, sin necesidad de definir manualmente una operación map
. Esta integración directa en el sistema de tipos permite composiciones más expresivas y verificables, y nos abre la puerta a herramientas como QuickCheck, que permiten verificar automáticamente que las leyes del funtor se cumplen.
En esta lección, verás cómo aprovechar esa integración, cómo se compara con Kotlin, y qué ventajas te ofrece Haskell al trabajar con el funtor función.
Instancia Functor para funciones en Haskell
instance Functor ((->) r) where
fmap f g = \x -> f (g x)
Esto quiere decir que si tienes una función g :: r -> a
, y una transformación f :: a -> b
, entonces fmap f g
te da una nueva función r -> b
. Esta operación es exactamente la misma que en Kotlin con map
.
✅ Verificando las leyes del funtor con QuickCheck
Una ventaja de Haskell es que puedes verificar automáticamente las leyes del funtor utilizando pruebas propiedadales con QuickCheck.
📐 Ley de Identidad
prop_identity :: (Eq b, Show r, Arbitrary r) => Fun r b -> r -> Bool
prop_identity (Fun _ f) x = (fmap id f) x == f x
Este test genera funciones arbitrarias f :: r -> b
y verifica que fmap id f ≡ f
.
📐 Ley de Composición
prop_composition :: (Eq c, Show r, Arbitrary r) => Fun b c -> Fun a b -> Fun r a -> r -> Bool
prop_composition (Fun _ f) (Fun _ g) (Fun _ h) x =
(fmap f (fmap g h)) x == (fmap (f . g) h) x
Esto prueba que aplicar dos veces fmap
equivale a aplicar una sola vez con la composición de funciones: fmap f . fmap g ≡ fmap (f . g)
.
🧪 Ejemplo de ejecución
import Test.QuickCheck
import Test.QuickCheck.Function
main :: IO ()
main = do
quickCheck (prop_identity :: Fun Int String -> Int -> Bool)
quickCheck (prop_composition :: Fun Bool Char -> Fun Int Bool -> Fun String Int -> String -> Bool)
Haskell infiere los tipos y genera automáticamente las funciones y datos necesarios para verificar las propiedades. Este nivel de automatización no es posible en Kotlin de forma directa.
🔄 Diferencias clave con Kotlin
Característica | Kotlin | Haskell |
---|---|---|
Sintaxis | Manual: se implementa map como extensión | Automática: (->) r es instancia de Functor |
Verificación de leyes | Manual (Kotest, assert) | Automática (QuickCheck) |
Composición de funciones | .map(f).map(g) | fmap g . fmap f |
Generalidad | Solo si se define explícitamente | Automática por el sistema de tipos |
Contrafuntores (Contravariant ) | Manual (contramap ) | Instancia de Contravariant disponible |
En Haskell, gracias a su sistema de clases de tipos, las funciones son funtores de forma nativa, y las leyes del funtor se pueden verificar automáticamente con herramientas como QuickCheck. En Kotlin, aunque se puede definir una operación map
equivalente, no existe soporte automático para validar las leyes del funtor: deben probarse de forma manual.