Search code examples
haskellfunctional-programming

Apply an endomorphism to elements of different types in a pair


In short, I would like the following to type-check in Haskell:

both f (x, y) = (f x, f y)

foo :: ([Int], [Char])
foo = ([1], "a")

bar :: ([Int], [Char])
bar = both (concat . replicate 3) foo  -- intended: ([1, 1, 1], "aaa")

The error I'm getting is:

• Couldn't match type ‘Char’ with ‘Int’
  Expected: ([Int], [Int])
    Actual: ([Int], [Char])
• In the second argument of ‘both’, namely ‘foo’
  In the expression: both (concat . replicate 3) foo
  In an equation for ‘bar’: bar = both (concat . replicate 3) foo

I got it to compile after I added the following type annotation to both:

both :: (forall x. [x] -> [x]) -> ([a], [b]) -> ([a], [b])
both f (x, y) = (f x, f y)

But this solution does not feel completely satisfactory because both seems like more generic, so that, for instance both id (1, "a") is accepted as well. Can I somehow go further from here?


Solution

  • I would consider this to be the most sensible version:

    both :: ∀ t a b . (∀ x . t x -> t x) -> (t a, t b) -> (t a, t b)
    both f (x,y) = (f x, f y)
    

    Your list example is a special case of this by way of t ~ [], but it can also used with other containers and indirectly also with "un-contained" values since you can always use Identity – though that is basically useless since the only function ∀ x . x -> x is id.