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:
Lo sé que
fmap :: (c -> d) -> f c -> f d
Si sustituimos
f
conEither a
obtenemosfmap :: (c -> d) -> Either a c -> Either a d
El tipo de
Right (g x)
esEither a (g x)
, y el tipo deg x
esd
, así tenemos que el tipo deRight (g x)
esEither a d
, que es lo que podemos esperar defmap
(ver 2. above)Ahora, si miramos
Left (g x)
podemos usar el mismo razonamiento para decir que su tipo esEither (g x) b
, es decirEither d b
, que no es lo que esperamos defmap
(ver 2. arriba): eld
debe ser el segundo parámetro, no ¡el primero! Así que no podemos mapear sobreLeft
.
Es mi razonamiento correcto?
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.
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.
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
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
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