haskellpolymorphismrank-n-types

How to apply a polymorphic function to both sides of an Either?


I tried this:

type TestT = Either Int Float

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

but it doesn't work, is there any way to do this? I did some looking around and everything similar was really complicated and limited.

Error message, as requesed:

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)

Solution

  • I don't think you can do that in the base language. As mentioned in the comments, you might need to enable a couple of extensions, such as RankNTypes.

    As all involved types are numeric ones, it is tempting to use an increment function, such as (+1) as the polymorphic function.

    Let's try under 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
     λ> 
    
    

    Note 1: If you omit the language extensions, it breaks, with a message hinting to RankNTypes.

    Note 2: If you use forall a. Num a => (a -> a) instead of (forall a. Num a => a -> a), it also breaks.

    Note 3: Some prior art here: SO-q38298119 with a useful comment from Alexis King.