tipos que monadas leer instancias hace gcd functores funciones funcion for example datos data haskell typeclass functor either

monadas - que hace en haskell



Comprender cómo O bien es una instancia de Functor (4)

Ahora, estoy tratando de entender por qué la implementación se correlaciona en el caso de un constructor de valor Derecho, pero no en el caso de un Izquierda?

Enchúfalo aquí y podría tener sentido.

Suponga que a = String (un mensaje de error) Usted aplica O a a Float.

Entonces tienes una f: Flotante -> Entero, por ejemplo redondeo.

(Either String) (Float) = Cualquiera de String Float.

ahora (fmap f) :: O String Float -> O String Int Entonces, ¿qué vas a hacer con f? f no tiene ni idea de qué hacer con las cadenas para que no pueda hacer nada allí. Es obvio que lo único en lo que puedes actuar son los valores correctos sin modificar los valores de la izquierda.

En otras palabras, O a es un funtor porque existe una fmap tan obvia dada por:

  • para valores correctos aplicar f
  • para valores Left no hacen nada

En mi tiempo libre, estoy aprendiendo Haskell, así que esta es una pregunta para principiantes.

En mis lecturas me encontré con un ejemplo que ilustra cómo se hace una de las dos instancias de Functor :

instance Functor (Either a) where fmap f (Right x) = Right (f x) fmap f (Left x) = Left x

Ahora, estoy tratando de entender por qué la implementación se correlaciona en el caso de un constructor de valor Right , pero no en el caso de un Left ?

Aquí está mi entendimiento:

Primero déjame reescribir la instancia anterior como

instance Functor (Either a) where fmap g (Right x) = Right (g x) fmap g (Left x) = Left x

Ahora:

  1. Sé que fmap :: (c -> d) -> fc -> fd

  2. si sustituimos f por Either a obtenemos fmap :: (c -> d) -> Either ac -> Either ad

  3. el tipo de Right (gx) es Either a (gx) , y el tipo de gx es d , entonces tenemos que el tipo de Right (gx) es Either ad , que es lo que esperamos de fmap (ver 2. más arriba)

  4. ahora, si miramos a la Left (gx) podemos usar el mismo razonamiento para decir que su tipo es Either (gx) b , es decir, Either db , que no es lo que esperamos de fmap (ver 2. arriba): el d debería ser el segundo parámetro, ¡no el primero! Entonces no podemos mapear sobre Left .

¿Mi razonamiento es correcto?


Como otros mencionaron, Either tipos es un funtor en sus dos argumentos. Pero en Haskell somos capaces de (directamente) definir solo funtores en los últimos argumentos de un tipo. En casos como este, podemos evitar la limitación mediante el uso de newtype s:

newtype FlipEither b a = FlipEither { unFlipEither :: Either a b }

Así que tenemos el constructor FlipEither :: Either ab -> FlipEither ba que envuelve un Either en nuestro newtype tipo con argumentos de tipo intercambiado. Y tenemos declabctor unFlipEither :: FlipEither ba -> Either ab que lo desenrolla. Ahora podemos definir una instancia de functor en el último argumento de FlipEither , que en realidad es el primer argumento de Either :

instance Functor (FlipEither b) where fmap f (FlipEither (Left x)) = FlipEither (Left (f x)) fmap f (FlipEither (Right x)) = FlipEither (Right x)

Tenga en cuenta que si olvidamos FlipEither por un tiempo obtenemos la definición de Functor para Either , solo con Left / Right intercambiada. Y ahora, cada vez que necesitamos una instancia de Functor en el primer argumento de tipo de Either , podemos ajustar el valor en FlipEither y desenvolverlo después. Por ejemplo:

fmapE2 :: (a -> b) -> Either a c -> Either b c fmapE2 f = unFlipEither . fmap f . FlipEither

Actualización: Eche un vistazo a Data.Bifunctor , del cual Either y (,) son instancias de. Cada bifunctor tiene dos argumentos y es un functor en cada uno de ellos. Esto se refleja en los métodos de Bifunctor first y second .

La definición de Bifunctor de Either es muy simétrica:

instance Bifunctor Either where bimap f _ (Left a) = Left (f a) bimap _ g (Right b) = Right (g b) first f = bimap f id second f = bimap id f


Esto es correcto. También hay otra razón bastante importante para este comportamiento: puede pensar en Either ab como un cálculo, que puede tener éxito y retornar o fallar con un mensaje de error a . (Esta es también, cómo funciona la instancia de la mónada). Entonces, es natural que la instancia del funtor no toque los valores de la Left , ya que desea mapear sobre el cálculo, si falla, no hay nada que manipular.


Su cuenta es correcta, por supuesto. Tal vez la razón por la que tenemos una dificultad con instancias como esta es que estamos definiendo infinitamente muchas instancias funcionadoras a la vez, una para cada posible tipo Left . Pero una instancia de Functor es una forma sistemática de operar en infinitos tipos en el sistema. Así que estamos definiendo infinitamente muchas formas de operar sistemáticamente en infinitos tipos en el sistema. La instancia implica generalidad de dos maneras.

Si lo tomas por etapas, tal vez no sea tan extraño. El primero de estos tipos es una versión larga de Maybe usando el tipo de unidad () y su único valor legítimo () :

data MightBe b = Nope () | Yep b data UnlessError b = Bad String | Good b data ElseInt b = Else Int | Value b

Aquí podríamos cansarnos y hacer una abstracción:

data Unless a b = Mere a | Genuine b

Ahora hacemos nuestras instancias de Functor, sin problemas, la primera que se parece mucho a la instancia de Maybe :

instance Functor MightBe where fmap f (Nope ()) = Nope () -- compare with Nothing fmap f (Yep x) = Yep (f x) -- compare with Just (f x) instance Functor UnlessError where fmap f (Bad str) = Bad str -- a more informative Nothing fmap f (Good x) = Good (f x) instance Functor ElseInt where fmap f (Else n) = Else n fmap f (Value b) = Value (f b)

Pero, de nuevo, ¿por qué molestarse, hagamos la abstracción:

instance Functor (Unless a) where fmap f (Mere a) = Mere a fmap f (Genuine x) = Genuine (f x)

Los términos Mere a no se tocan, ya que los valores () , String e Int no se tocaron.