¿Por qué la instancia de funtor de 2 tuplas solo aplica la función al segundo elemento?


import Control.Applicative

main = print $ fmap (*2) (1,2)

Produce (1,4). Esperaría que produjera (2,4) pero en cambio la función se aplica solo al segundo elemento de la tupla.

Actualizar Básicamente he descubierto esto casi de inmediato. Voy a publicar mi propia respuesta en un minuto..

Author: Peter Hall, 2012-11-18

3 answers

Déjame responder esto con una pregunta: ¿Qué salida esperas para:

main = print $ fmap (*2) ("funny",2)

Usted puede tener algo como desee (usando data Pair a = Pair a a más o menos), pero como (,) puede tener diferentes tipos en su primer y segundo argumento, no tiene suerte.

 30
Author: Landei,
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-18 17:59:20

La instancia Functor es en realidad del GHC.Módulo base que es importado por Control.Applicative.

Tratando de escribir la instancia que quiero, puedo ver que no funcionará, dada la definición de tuplas; la instancia requiere solo un parámetro de tipo, mientras que la tupla 2 tiene dos.

Una instancia válida Functor al menos tendría que estar en tuplas, (a,a) que tienen el mismo tipo para cada elemento, pero no se puede hacer nada furtivo, como definir la instancia en:

 type T2 a = (a,a)

Porque no se permite que los tipos de instancia sean sinónimos.

El sinónimo de 2-tupla restringido anterior es lógicamente el mismo que el tipo:

data T2 a = T2 a a

Que puede tener una instancia de Funtor:

instance Functor T2 where
    fmap f (T2 x y) = T2 (f x) (f y)

Como Gabriel comentó en los comentarios, esto puede ser útil para ramificar estructuras o concurrencia.

 13
Author: Peter Hall,
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
2016-01-04 23:15:25

Los pares se definen, esencialmente, de la siguiente manera:

data (,) a b = (,) a b

La clase Functor se ve así:

class Functor f where
  fmap :: (a -> b) -> f a -> f b

Dado que los tipos de argumentos de función y los resultados deben tener tipo * (es decir, representan valores en lugar de funciones de tipo que se pueden aplicar más o cosas más exóticas), debemos tener a :: *, b :: *, y, lo más importante para nuestros propósitos, f :: * -> *. Desde (,) tiene * -> * -> *, debe aplicarse a un tipo de clase * para obtener un tipo adecuado para ser un Functor. Así

instance Functor ((,) x) where
  -- fmap :: (a -> b) -> (x,a) -> (x,b)

Así que en realidad no hay manera de escribir una instancia Functor haciendo otra cosa.


Una clase útil que ofrece más formas de trabajar con pares es Bifunctor, de Data.Bifunctor.

class Bifunctor f where
  bimap :: (a -> b) -> (c -> d) -> f a c -> f b d
  bimap f g = first f . second g

  first :: (a -> b) -> f a y -> f b y
  first f = bimap f id

  second :: (c -> d) -> f x c -> f x d
  second g = bimap id g

Esto te permite escribir cosas como las siguientes (desde Data.Bifunctor.Join):

  newtype Join p a =
    Join { runJoin :: p a a }

  instance Bifunctor p => Functor (Join p) where
    fmap f = Join . bimap f f . runJoin

Join (,) es entonces esencialmente lo mismo que Pair, donde

data Pair a = Pair a a

Por supuesto, también puede usar la instancia Bifunctor para trabajar con pares directamente.

 12
Author: dfeuer,
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
2016-01-05 04:38:31