¿Cómo aplicar una función polimórfica a ambos lados de un Either?
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
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.
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>