Entender cómo Cualquiera de los dos es una instancia de Funtor


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 Either a se hace una instancia 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 mapea en el caso de un constructor de valor Right, pero no lo hace en el caso de un Left?

Aquí está mi entendimiento:

Primero permítanme 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. Lo sé que fmap :: (c -> d) -> f c -> f d

  2. Si sustituimos f con Either a obtenemos fmap :: (c -> d) -> Either a c -> Either a d

  3. El tipo de Right (g x) es Either a (g x), y el tipo de g x es d, así tenemos que el tipo de Right (g x) es Either a d, que es lo que podemos esperar de fmap (ver 2. above)

  4. Ahora, si miramos Left (g x) podemos usar el mismo razonamiento para decir que su tipo es Either (g x) b, es decir Either d b, que no es lo que esperamos de fmap (ver 2. arriba): el d debe ser el segundo parámetro, no ¡el primero! Así que no podemos mapear sobre Left.

Es mi razonamiento correcto?

Author: Petr Pudlák, 2011-03-04

4 answers

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

 20
Author: fuz,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-04 14:56:48

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

Si aunque lo tomas por etapas, tal vez no sea tan extraño. El primero de estos tipos es una versión longwinded 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: {[14]]}

data Unless a b    = Mere a     | Genuine b

Ahora hacemos nuestras instancias de Funtor, sin problemas, las primeras pareciéndose 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, para qué molestarse, hagamos la abstracción: {[14]]}

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

Los términos Mere a no son tocada, como la (), String y Int los valores no fueron tocados.

 9
Author: applicative,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-04 16:20:56

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

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

Así que tenemos constructor FlipEither :: Either a b -> FlipEither b a que envuelve un Either en nuestro newtype con argumentos de tipo intercambiado. Y tenemos destructor unFlipEither :: FlipEither b a -> Either a b que lo desenvuelve de nuevo. Ahora podemos definir una instancia de funtor en el último argumento de FlipEither, que en realidad es el primer argumento de Either argumento:

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

Observe que si olvidamos FlipEither por un tiempo obtenemos solo la definición de Functor para Either, solo con Left/Right intercambiados. Y ahora, cada vez que necesitamos una instancia Functor en el primer argumento de tipo Either, podemos envolver 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: Echa un vistazo a los datos de .Bifunctor, de los cuales 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 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
 4
Author: Petr Pudlák,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2012-11-11 07:51:13

Ahora, estoy tratando de entender por qué la mapas de implementación en el caso de Constructor de valor correcto, pero no ¿en el caso de una izquierda?

Conecte aquí y podría tener sentido.

Asume a = String ( un mensaje de error) Aplicar a un Flotador.

Así que tienes un f: Float -> Integer digamos por ejemplo redondeo.

(Cualquiera de las cadenas) (Float) = Cualquiera de las Cadenas Float.

Now (fmap f):: Cualquiera de las cadenas Float - > Cualquiera de las cadenas Int Tan ¿qué vas a hacer con f? f no tiene ni idea de qué hacer con las cuerdas, así que no puedes hacer nada allí. Es decir obviamente lo único sobre lo que puedes actuar son los valores de la derecha mientras dejas los valores de la izquierda sin cambios.

En otras palabras, o bien a es un funtor porque hay un fmap tan obvio dado por:

  • para los valores correctos aplicar f
  • para los valores de la izquierda no hacer nada
 1
Author: jbolden1517,
Warning: date(): Invalid date.timezone value 'Europe/Kyiv', we selected the timezone 'UTC' for now. in /var/www/agent_stack/data/www/ajaxhispano.com/template/agent.layouts/content.php on line 61
2011-03-04 18:43:51