Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Apply a polymorphic function to two different types of inputs [duplicate]

Consider this function:

doToBoth f x y = (f x, f y)

It works as expected in simple cases:

doToBoth (2 *) 10 15 == (20, 30)
doToBoth head [1,2] [3,4,5] == (1, 3)

Then I tried these ones:

doToBoth head [1,10,100] "apple"
doToBoth pred 2 'b'

I want those to both result in (1, 'a'), but instead they just result in a type error. The problem is that the inferred type of doToBoth isn't polymorphic enough:

doToBoth :: (a1 -> a2) -> a1 -> a1 -> (a2, a2)

Seeing this, I tried adding an explicit type signature to fix it:

doToBoth :: (t ~ (i1 -> o1), t ~ (i2 -> o2)) => t -> i1 -> i2 -> (o1, o2)

This type signature was accepted, but it didn't fix the problem, and checking what happened with :t doToBoth revealed that it ended up with a type equivalent to the original inferred one:

doToBoth :: (i2 -> o2) -> i2 -> i2 -> (o2, o2)

What is the correct way to write a type signature to make this function work the way I want?

like image 703
Joseph Sible-Reinstate Monica Avatar asked Jan 10 '19 22:01

Joseph Sible-Reinstate Monica


1 Answers

Accepting a polymorphic argument makes your function rank-2 polymorphic. GHC has an extension for that, but you can only use it if you can somehow quantify over what kinds of types the argument must support – with type constructors or -classes. For instance, for the case of lists you could write

{-# LANGUAGE Rank2Types, UnicodeSyntax #-}
doToBoth_listelem :: (∀ x . [x] -> x) -> [a] -> [b] -> (a,b)
doToBoth_listelem f x y = (f x, f y)
> doToBoth_listelem head [1,10,100] "apple"
(1,'a')

This would also work for the pred example, and is somewhat more useful. In this case you need to quantify over an argument that's constrained by the Enum class:

doToBoth_enum :: (Enum a, Enum b)
      => (∀ x . Enum x => x -> x) -> a -> b -> (a,b)
doToBoth_enum f x y = (f x, f y)
> doToBoth_enum pred 2 'b'
(1,'a')

Writing it so it automatically works for any such constraint that the argument might require is, I think, not possible. It might be possible to approximate that with some clever type families and constraint kinds, but I doubt it would end up being practically usable.

like image 155
leftaroundabout Avatar answered Oct 20 '22 12:10

leftaroundabout