The problem here is that you have an ambiguous type. Firstly, let’s check the type signature of test
as inferred by GHC. A neat trick I discovered a while ago is to add test :: _
to your program and let GHC give us its inferred type in an error message:
so.hs:13:9: error:
• Found type wildcard ‘_’
standing for ‘(b0 > b0 > b0, Integer > Integer > Integer,
Integer > Integer > Integer)’
Where: ‘b0’ is an ambiguous type variable
To use the inferred type, enable PartialTypeSignatures
• In the type signature: test :: _

13  test :: _
 ^
So the type of test
as inferred by GHC is (b0 > b0 > b0, Integer > Integer > Integer, Integer > Integer > Integer)
(although there should be an additional (Ord b0, Bounded b0)
constraint which GHC leaves out for some reason). Now, let’s look at resF
and resG
:
(resF, _, _) = test
(_, resG, _) = test
In the definition of resF
, the b0
type parameter ends up being used outside that expression as well (in the type of resF :: b0 > b0 > b0
), so it isn’t ambiguous. However, in the definition of resG
, b0
is only ever used inside that expression, so it could be anything! Since GHC has absolutely no way to determine what b0
is in that declaration, it is marked as ambiguous, producing this error.
(If that wasn’t clear enough, the idea is that if you have an expression with an ambiguous type variable, and you refer to this variable on the left side of the =
, then it becomes disambiguated, as the variable is being used outside the expression. I know this isn’t a very good explanation; I’m not too good with this area of Haskell myself, so if anyone else has a better explanation please comment!)
So how can this problem be solved? One way is simply to combine resF
and resG
, so b0
does end up being used outside test
:
(resF, resG, _) = test
Another way is to add a type signature restricting b0
:
(_, resG, _) = test :: (() > () > (), Integer > Integer > Integer, Integer > Integer > Integer)
This is the most common way of getting around ambiguous type errors, as it will work in all circumstances. In this case it happens to be much longer, but you should be able to use it in more situations than the above technique, which really only works here.
However, there’s still a few subtle points here. Firstly, why does GHC report that the second and third fields use Integer
, instead of allowing any type? This is due to the monomorphism restriction, which in certain situations specialises type variables automatically. You can get around this by adding a type signature:
test :: (Ord a, Bounded a, Num b, Num c) => (a > a > a, b > b > b, c > c > c)
This is why it is considered good practise to add type signatures to all functions!
Of course, this has the disadvantage of making the second and third fields use type variables as well; hence, they become prone to ambiguous types as well. You can get around this by binding all three fields to allow these type variables to ‘propagate’ in a sense outside that declaration:
(resF, resG, resH) = test
(Note that ‘propagate’ is my own term, not a recognised Haskell term!)
EDIT: So, it turns out this strategy doesn’t work. More details are given at the end of this answer, since it’s a bit detailed.
Or you can add a type signature again to restrict b
and c
:
(resF, _, _) = test :: (Ord a, Bounded a) => (a > a > a, Int > Int > Int, Int > Int > Int)
The other point I wanted to make is with the definition of `test` itself. In Haskell, it is very uncommon to use global variables as you do here; usually you would add them as parameters to `test`, then pass them in from outside like this:
test :: (Ord a, Bounded a, Num b, Num c)
=> Bool
> Bool
> (a > a > a, b > b > b, c > c > c)
test a b =
 a == b = (const, const, const)
 a = (f, (), (+))
 b = (g, (+), ())
(resF, resG, resH) = test True False
Doing it this way allows for greater reuse of code, as test
can now be used multiple times with different boolean conditions.
EDIT:
Limitations of polymorphic tuples
I’m not sure the above is incorrect as such, but there’s an important factor which I completely missed. If you have something of type (Constr1 a, Constr2 b) => (a, b)
, the entire tuple depends on both Constr1 a
and Constr2 b
! So you can’t easily remove one type variable to isolate the other. (More details in this excellent answer.)
However, there is a solution! In test
, each field is independent of each other. So it should theoretically be possible to change the type to the following:
test :: Bool > Bool
> ( forall a. (Ord a, Bouded a) => a > a > a
, forall b. Num b => b > b > b
, forall c. Num c => c > c > c
)
test a b =
 a == b = (const, const, const)
 a = (f, (), (+))
 b = (g, (+), ())
Now all the constraints have in a sense been ‘pulled into’ the tuple, so you can now isolate one field.
Of course, nothing is ever quite as simple as that, and if you try running the above you run into an error about ‘impredicative polymorphism’. The solutions is wrapping the fields in auxilliary data types:
newtype Wrapper1 = Wrapper1 (forall a. (Ord a, Bounded a) => a > a > a)
newtype Wrapper2 = Wrapper2 (forall b. Num b => b > b > b)
test :: (Wrapper1, Wrapper2, Wrapper2)
test
 a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
 a = (Wrapper1 f , Wrapper2 () , Wrapper2 (+))
 b = (Wrapper1 g , Wrapper2 (+) , Wrapper2 ())
(Wrapper1 resF, Wrapper2 resG, Wrapper2 resH) = test
(You’ll also need to add {# LANGUAGE RankNTypes #}
to the beginning of the file to get this to compile.)
And this — finally! — typechecks successfully.
As a further upside, it turns out that this method even gets rid of ambiguous type errors. The following code typechecks successfully as well:
test
 a == b = (Wrapper1 const, Wrapper2 const, Wrapper2 const)
 a = (Wrapper1 f , Wrapper2 () , Wrapper2 (+))
 b = (Wrapper1 g , Wrapper2 (+) , Wrapper2 ())
(Wrapper1 resF, _, _) = test
As I mentioned above, I don’t understand ambiguous types too well, but the reason for this is probably because all the information about other type variables has been ‘pulled into’ the other fields of the tuple, so GHC knows it can safely ignore them now.
test
,resF
, andresG
, and see if the error becomes more clear.XNoMonomorphismRestriction
.