2010-02-25 15 views
11

escribí algo como esto:Manipulando el orden de los argumentos para escribir constructores

instance Functor (Either e) where 

    fmap _ (Left a) = Left a 

    fmap f (Right b) = Right (f b) 

¿Cómo hago la misma si quiero fmap para cambiar el valor sólo si es Left?

Es decir, ¿qué sintaxis uso para indicar que utilizo el tipo Either _ b en lugar de Either a _?

+1

Relacionado: http://stackoverflow.com/questions/1827645 En resumen, no es posible en Haskell tal como está ahora. – ephemient

+0

Como un aparte, el título de esta pregunta es poco claro, pero no estoy seguro de qué sería mejor. –

+0

Propuse un título más preciso. –

Respuesta

7

No creo que haya una manera de hacerlo directamente, desafortunadamente. Con una función puede usar flip para aplicar parcialmente el segundo argumento, pero eso no funciona con constructores de tipo como Either.

Lo más sencillo es, probablemente, envolviéndolo en una newtype:

newtype Mirror b a = Mirrored (Either a b) 

instance Functor (Mirror e) where 
    fmap _ (Mirrored (Right a)) = Mirrored $ Right a 
    fmap f (Mirrored (Left b)) = Mirrored $ Left (f b) 

de envolver con newtype es también la forma estándar para crear varias instancias de un solo tipo, tales como Sum y Product siendo ejemplos de Monoid para numérico tipos. De lo contrario, solo puede tener una instancia por tipo.

Además, dependiendo de qué es lo que quiere hacer, otra opción es hacer caso omiso de Functor y definir su propio tipo de clase de esta manera:

class Bifunctor f where 
    bimap :: (a -> c) -> (b -> d) -> f a b -> f c d 

instance Bifunctor Either where 
    bimap f _ (Left a) = Left $ f a 
    bimap _ g (Right b) = Right $ g b 

instance Bifunctor (,) where 
    bimap f g (a, b) = (f a, g b) 

Obviamente, esa clase es dos veces tan divertido como un habitual Functor. Por supuesto, no puede hacer una instancia Monad de eso muy fácilmente.

+1

¿Qué pasa con 'newtype Flip t a b = Flip (t b a)' y luego 'instance Functor (Flip Either e)'? –

+0

No funciona: "Todos los tipos de instancia deben tener la forma (T a1 ... an) donde a1 ... an son tipo * variables *" – mik01aj

+0

@ m01: Funciona, pero requiere habilitar una extensión de idioma GHC . En la práctica, cualquiera (sin juego de palabras) lo haría como Norman Ramsey sugiere o usa la clase 'Bifunctor' como en mi respuesta. El tipo sobreespecífico 'Mirror' estaba destinado principalmente a ilustrar la idea. –

3

Esencialmente necesita un combinador 'flip' en los tipos. Un nuevo envoltorio de tipo que invierta la orden debería funcionar, como dice la cámara oculta. Tenga en cuenta que no puede usar un sinónimo 'tipo', ya que es posible que no se apliquen parcialmente.

4

No puede hacer la instancia que está buscando directamente.

Para que las clases de tipo y de inferencia funcionen, hay un cierto sesgo posicional al orden de los argumentos en los tipos. Se ha demostrado que si permitimos el reordenamiento arbitrario de los argumentos al crear instancias de clases de tipos, la inferencia de ese tipo se vuelve intratable.

Puede usar una clase Bifunctor que puede asignar ambos argumentos por separado.

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

    first f = bimap f id 
    second = bimap id 

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

instance Bifunctor (,) where 
    bimap f g (a,b) = (f a, g b) 

O usted podría utilizar un combinador Flip como:

newtype Flip f a b = Flip { unFlip :: f b a } 

versiones generalizadas de ambos de estos están disponibles en la categoría-extras en hackage. Este último incluso incluye una instancia para Functor (Flip Either a) porque Either es un Bifunctor. (Probablemente debería arreglar eso para solo requerir un PFunctor)

En última instancia, el orden de los argumentos en un constructor de tipos es importante para determinar qué clases puede crear instancias. Es posible que necesite usar envoltorios de tipo nuevo (como Flip anterior) para colocar los argumentos donde deben estar para calificar para construir una instancia de otra clase de tipo. Este es el precio que pagamos por la inferencia de las restricciones de clase de tipo.

Cuestiones relacionadas