¿Cómo aplicar una función polimórfica a ambos lados de un Either?

Dec 06 2020

Probé esto:

type TestT = Either Int Float

testM :: (a -> a) -> TestT -> TestT
testM f (Left x) = Left (f x)
testM f (Right x) = Right (f x)

pero no funciona, ¿hay alguna forma de hacer esto? Miré a mi alrededor y todo lo similar fue realmente complicado y limitado.

Mensaje de error, como se solicitó:

Main.hs:101:28: error:
    • Couldn't match expected type ‘a’ with actual type ‘Int’
      ‘a’ is a rigid type variable bound by
        the type signature for:
          testM :: forall a. (a -> a) -> TestT -> TestT
        at Main.hs:100:1-35
    • In the first argument of ‘f’, namely ‘x’
      In the first argument of ‘Left’, namely ‘(f x)’
      In the expression: Left (f x)
    • Relevant bindings include
        f :: a -> a (bound at Main.hs:101:7)
        testM :: (a -> a) -> TestT -> TestT (bound at Main.hs:101:1)

Respuestas

3 jpmarinier Dec 06 2020 at 03:30

No creo que puedas hacer eso en el idioma base. Como se menciona en los comentarios, es posible que deba habilitar un par de extensiones, como RankNTypes.

Como todos los tipos involucrados son numéricos, es tentador usar una función de incremento, como (+1) como función polimórfica.

Probemos a continuación ghci:

$ ghci
GHCi, version 8.6.5: http://www.haskell.org/ghc/  :? for help
 λ> 
 λ> type TestT = Either Int Float 
 λ> 
 λ> :set +m
 λ> 
 λ> :set -XRankNTypes
 λ> :set -XScopedTypeVariables
 λ> 
 λ> {-
|λ> let { testM :: (forall a. Num a => a -> a) -> TestT -> TestT ;
|λ>       testM fn (Left x) = Left (fn x) ;
|λ>       testM fn (Right x) = Right (fn x) }
|λ> -}
 λ> 
 λ> :type testM
testM :: (forall a. Num a => a -> a) -> TestT -> TestT
 λ> 
 λ> testM (+3) (Left 42)
Left 45
 λ> 
 λ> testM (+3) (Right 3.14159)
Right 6.14159
 λ> 

Nota 1: Si omite las extensiones de idioma, se interrumpe, con un mensaje que indica RankNTypes.

Nota 2: Si usa en forall a. Num a => (a -> a)lugar de (forall a. Num a => a -> a), también se rompe.

Nota 3: Un poco de técnica anterior aquí: SO-q38298119 con un comentario útil de Alexis King.

2 DavidFox Dec 09 2020 at 10:26

Una forma de hacer esto es con Bifunctor:

Prelude> :m +Data.Bifunctor
Prelude Data.Bifunctor> bimap show show (Left 3)
Left "3"
Prelude Data.Bifunctor> bimap show show (Right 'x')
Right "'x'"
Prelude Data.Bifunctor>