2

Minimal example code:

class IntegralAsType a where
  value :: (Integral b) => a -> b

class (Num a, Fractional a, IntegralAsType q) => Zq q a | a -> q where
  changeBase:: forall p b . (Zq q a, Zq p b) => a -> b

newtype (IntegralAsType q, Integral a) => ZqBasic q a = ZqBasic a deriving (Eq)

zqBasic :: forall q a . (IntegralAsType q, Integral a) => a -> ZqBasic q a
zqBasic x = ZqBasic (mod x (value (undefined::q)))

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = fromIntegral (value (undefined::p)) -- ZqBasic is an instance of Num

Here's a little background on what I'm trying to accomplish: IntegralAsType ensures type safety at compile time by preventing something like addition of two numbers with different modulus. ZqBasic is an internal representation of the Zq type, there are others, which is why Zq is defined the way it is. The goal is to get a system that is transparent to the internal representation.

My problem is with the changeBase function. I'm using explicit forall on the 'p' type, but I still get an "ambiguous type variable a0 in the constraint (IntegralAsType a0) arising from the use of value"

I'm confused on why I'm getting this error. In particular in a previous post, I got help with something like the "zqBasic" function, which appears to have the same setup as the changeBase function. I fixed the ambiguous variable error in zqBasic by adding the explicit quantifier 'forall q a .' Without this quantifier, I get an ambiguous type variable error. I understand why I need the quantifier there, but I don't understand why it doesn't seem to be helping for changeBase.

Thanks

crockeea
  • 21,651
  • 10
  • 48
  • 101
  • Extra notes: I'm using the ScopedTypeVariables, FlexibleInstances, MultiParamTypeClasses, and FunctionalDependencies extensions. The code above works when (undefined::p) is replaced by (undefined::q) in 'changeBase' – crockeea Dec 02 '11 at 14:49

3 Answers3

3

Using ScopedTypeVariables isn't helping here because the p you're using isn't in scope anyway, it seems. Compare the following definitions:

changeBase (ZqBasic x) = fromIntegral (value (undefined::foobar))

This gives the same error, because it's also creating a new type variable.

changeBase (ZqBasic x) = fromIntegral (value (5::p))

This, however, gives a different error. The relevant bits are:

Could not deduce (Num p1) arising from the literal `5'
        (snip)
    or from (Zq q (ZqBasic q a), Zq p b)
      bound by the type signature for
                 changeBase :: (Zq q (ZqBasic q a), Zq p b) => ZqBasic q a -> b

This shows that p is being instantiated as a fresh type variable. I'm guessing that the forall on the type signature doesn't bring into scope (in actual declarations) the type variables that aren't the class's type parameters. It does bring the variable into scope for a default declaration, however.

Anyway, that's neither here nor there.

It's easy enough to work around most problems with needing access to type variables--just create some auxiliary functions that don't do anything but let you manipulate the types appropriately. For instance, a nonsense function like this will pretend to conjure up a term with the phantom type:

zq :: (Zq q a) => a -> q
zq _ = undefined

This is basically just giving you direct term-level access to the functional dependency, since the fundep makes q unambiguous for any particular a. You can't get an actual value, of course, but that's not the point. If the undefined bothers you, use [] :: [q] instead to similar effect and use head or whatever only when you need to.

Now you can twist things around a bit using a where clause or whatnot to forcibly infer the correct types:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = b
    where b = fromIntegral (value (zq b))

What's going on here is that b is the actual thing we want, but we need value to see the type p which is determined by the type of b, so by assigning a temporary name to the result we can use that to get the type we need.

In many cases you could also do this without the extra definition, but with type classes you need to avoid the ad-hoc polymorphism and ensure it doesn't allow the "recursion" to involve other instances.

On a related note, the standard library function asTypeOf is for exactly this type of fiddling with types.

C. A. McCann
  • 76,893
  • 19
  • 209
  • 302
  • Just to be clear, you have two 'b's in the code above: the first two are local variables, the third occurrence refers to the type from the signature, correct? – crockeea Dec 02 '11 at 15:32
  • @Eric: In the final code snippet, all three uses of `b` refer to the same thing; it doesn't mention types at all. In fact, avoiding the need to refer to types is the goal. – C. A. McCann Dec 02 '11 at 15:36
1

The call value (undefined::p) converts from p to some type a0. From the type of value, the only thing we can figure out is that a0 is an integral type.

That value is passed to fromIntegral, which converts from a0 to b. From the type of fromIntegral, the only thing we can figure out is that a0 is an integral type.

Nothing determines what type a0 is, so a type annotation is needed on the expression value (undefined::p) to resolve the ambiguity. From looking at your type signatures, it looks like value should be able to generate the correct return type without doing any extra conversions. Can you simply remove the call to fromIntegral?

Edit

You need to enable the ScopedTypeVariables extension. Without ScopedTypeVariables, a type variable cannot be mentioned in more than one type signature. In this case, the variable name p does not refer to the same variable in the function's type signature and in its body.

The following code works for me:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase = zqBasicChangeBase

zqBasicChangeBase :: forall p b q a.
  (IntegralAsType q, IntegralAsType p, Integral a, Zq p b) => ZqBasic q a -> b
zqBasicChangeBase (ZqBasic x) = fromIntegral (value (undefined :: p) :: Integer)
Heatsink
  • 7,721
  • 1
  • 25
  • 36
  • Thanks for the clear explanation, but that doesn't seem to work. Shouldn't fromIntegral work with any (arbitrary) integral value and convert it to "the right" type? Based on the context of the return type, the correct type of the call to fromIntegral should be inferred, I think. Also, for what it's worth, the code works fine if I use a "(value (undefined::q))" instead of undefined::p, so the problem appears to be because p is not bound by an argument? – crockeea Dec 02 '11 at 13:51
  • I have also tried explicitly stating the type of the call to value, ie ((value (undefined::p))::Integer), but get the same error, so the problem doesn't seem to be with an ambiguous type from value. – crockeea Dec 02 '11 at 13:54
  • You need the `ScopedTypeVariables` extension for the type signatures to mean what you want. It compiles for me with that extension turned on. – Heatsink Dec 02 '11 at 14:32
  • Very interesting, because I'm already using ScopedTypeVariables. I'm using GHCi 7.0.3 – crockeea Dec 02 '11 at 14:40
  • 3
    The very first thing I did when trying to compile your code was to move the definition of `changeBase` to a separate function and write its type signature. Type variables introduced in a class definition aren't in scope in the class instances. – Heatsink Dec 02 '11 at 14:58
  • It seems to me that "type variables introduced in a class definition" in my example only refers to 'a' and 'q' rather than 'b' and 'p', or does this apply to type variables in the type signature of any function in a class? At any rate, the *entire* point of this example is to allow changing bases between different types of Zqs. I don't want to have a separate function for each ZqInstance type, that's why I'm using a class! Any way around this? – crockeea Dec 02 '11 at 15:10
  • Type variables appearing anywhere in a class definition are scoped over the class definition (or some subexpression of the class definition). This applies to a, q, b, and p. Putting the code in a new function doesn't limit how you use the class, it just moves the code outside the class definition and introduces some noise. Anyway, camccann seems to have a nicer solution. – Heatsink Dec 02 '11 at 17:26
0

Two (essentially equivalent?) approaches that work:

1: Using C.A. McCann's approach:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase (ZqBasic x) = b
    where b = fromIntegral ((value (zq b))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope

2: Rather than having the signature a->b, I changed the signature of changeBase to b->a The following then works:

instance (IntegralAsType q, Integral a) => Zq q (ZqBasic q a) where
  changeBase x = fromIntegral ((value (zq x))*(fromIntegral (value (zq q)))) -- note that 'q' IS in scope

The goal was always to be able to access the type parameters for both the argument and return type, and both of these approaches allow me to do that.

Also, the answer to my question about the difference between the 'zqBasic' constructor and 'changeBase' is, as C.A.McAnn indicated, that 'p' was not put into scope in declarations, even with an explicit forall. If anyone can explain why this is, I'd appreciate it.

crockeea
  • 21,651
  • 10
  • 48
  • 101