2011-03-04 11 views
31

En mi tiempo libre, estoy aprendiendo Haskell, por lo que esta es una pregunta para principiantes.Entender cómo O bien es una instancia de Functor

En mis lecturas me encontré con un ejemplo que ilustra cómo se hace Either a 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é los mapas de aplicación en el caso de un constructor Right valor, pero no en el caso de un Left?

Aquí es mi entendimiento:

primer lugar quiero reescribir el ejemplo 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) -> f c -> f d

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

  3. del tipo de Right (g x) es Either a (g x), y el tipo de g x es d, por lo que tenemos que el tipo de Right (g x) es Either a d, que es lo que esperamos de fmap (véase el punto 2 anterior)

  4. ahora, si nos fijamos en Left (g x) podemos utilizar 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 (véase el punto 2 anterior): el d debería ser el segundo parámetro ¡No el primero! Por lo tanto, no podemos asignar más de Left.

¿Mi razonamiento es correcto?

+11

O es tal vez más, obviamente, un bifuntor que un Functor - un bifuntor tiene una operación BIMAP - BIMAP :: (a -> m) -> (b -> n) -> fab -> fm n. Esto le da un mapeo en ambos casos, Izquierda y Derecha. La biblioteca estándar de Haskell no tiene una clase Bifunctor, esto se debe a que hay muchos menos Bifunctors que Functors "en la naturaleza", aunque es útil para Either y pair (a, b). –

+1

En 3, usa (g x) como parte de un tipo, pero es un valor. Parece que quisiste escribir 'typeof (g x)' allí, y no '(g x)' en sí mismo. – Peaker

+0

@stephen tetley: ¡eso es interesante! Gracias – MarcoS

Respuesta

20

Esto es correcto. También hay otro motivo bastante 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. (Esta es también, cómo funciona la instancia de la mónada). Por lo tanto, es natural que la instancia del administrador no toque los valores Left, ya que desea asignar un mapa sobre el cálculo, si falla, no hay nada que manipular.

+0

Todavía no he aprendido sobre mónadas :) He leído que 'O ab' se usa de la manera que se describe, y que 'a' representa el mensaje de error: pero esto es solo una convención, ¿no? – MarcoS

+0

@MarcoS: correcto, esto es solo una convención. Cualquiera de las dos podría interpretarse como una disyunción: un valor de tipo O bien a b contiene un valor de tipo a o un valor de tipo b, pero no ambos. –

+0

En el caso individual: "Cadena de cadenas", "Cadena de caracteres", "Intérprete de cadenas", etc., esto es así, por supuesto; no hay diferencia entre los tipos derecho e izquierdo. Pero como un funtor puede fmap over - como (Either String), en esos ejemplos - hay una asimetría completa. El tipo que ingresa en la instancia concreta de Functor (aquí 'String') tiene que representar algo general que se puede encontrar en todos los cálculos ordinarios de cualquier tipo (lo que sucede a la derecha, a través de' fmap'), por lo que un 'error' 'interpretación, aunque apenas el único, no es un accidente. – applicative

8

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 functor 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 prolija de Maybe utilizando 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 cansar y hacer una abstracción:

data Unless a b = Mere a  | Genuine b 

Ahora hacemos nuestros casos Functor, sin problemas, el primero parecerse 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é preocuparse, vamos a hacer la abstracción:

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

Los Mere a términos no se tocan, como los (), String y Int valores no fueron tocados.

+0

Gracias, esta es también una buena explicación. Sin embargo, acepté la publicación de FUZxxl como respuesta porque, además de confirmar mi razonamiento, también me da una pista interesante sobre la interpretación de "O bien un b" como cálculo (mónada). – MarcoS

1

Ahora, estoy tratando de entender por qué los mapas de aplicación en el caso de un constructor valor correcto, pero no en el caso de una izquierda?

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

Asumir a = Cadena (un mensaje de error) Se aplica Ya sea a a Float.

Así que tiene una f: Flotante -> Entero, por ejemplo, redondeo.

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

ahora (fmap f) :: Either 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 decir, obviamente, en lo único que puede actuar son los valores correctos sin modificar los valores de la izquierda.

En otras palabras bien A es un functor porque hay una fmap tan obvia dada por:

  • para los valores de la derecha se aplican f
  • para los valores de izquierda no hacer nada
4

Como otros han mencionado , Either tipo 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 conseguir alrededor de la limitación utilizando 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 nuestras newtype con argumentos de tipo intercambiadas. Y tenemos dectructor unFlipEither :: FlipEither b a -> Either a b que lo desenvuelve.Ahora podemos definir una instancia funtor en FlipEither 'último argumento s, que es en realidad Either' s primer argumento:

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 sólo la definición de Functor para Either, justo con Left/Right intercambiado. Y ahora, siempre que necesitemos una instancia de 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: Tenga una mirada en Data.Bifunctor, de los cuales Either y (,) son ejemplos de. Cada bifunctor tiene dos argumentos y es un functor en cada uno de ellos. Esto se refleja en los métodos Bifunctorfirst 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 
Cuestiones relacionadas