On Friday 29 July 2011, 18:14:30, Jake Penton wrote:
On 2011-07-29, at 11:22 AM, Brandon Allbery wrote:
Ask yourself this: is True *every* instance of Ord? You are expecting it to be an "any", but it's an "every" (forall).
By the way, True happens to be an instance of Ord but it doesn't have to be. You're working backwards here, I think. It happens that useful operations on things in class Ord generally produce Bool; that doesn't mean Bool must be Ord.
Right - actually, I did not expect it to compile. I gave the code as, perhaps, a counterexample to what some other responses seemed to be asserting, although it is possible I did not get their point(s) correctly. They seemed to say that adding a constraint is of itself what makes the cases that used numeric literals work.
Given the fact that per the report a numeric literal is interpreted as a call to fromInteger or fromRational (depending on whether it's an integer literal or a fractional literal), the addition of the constraint is all that is needed to make it work, since by those functions' types, the value represented by a numeric literal is polymorphic. f = 1 is really f = fromInteger 1& (where n& stands for the Integer of the appropriate value, it's not Haskell; in GHC [with integer-gmp], it would be f = fromInteger (S# 1#) where S# is a constructor of Integer [MagicHash extension required to use it] and 1# is a literal of type Int#, raw signed machine int, four or eight bytes of raw memory). True, on the other hand, is a monomorphic value of type Bool, it cannot have any other type than Bool and trying to specify any other type for it leads to a compile time error.
But it seems (as described in Brent Yorgey's post) that there is more to it than that.
The fundamental difference is monomorphic vs. polymorphic expressions. A monomorphic expression like [True] has a specific type, it doesn't make sense to add constraints to it (but it makes sense to ask whether constraints are satisfied for using it). A polymorphic expression like (toEnum 0) can have many types, but in general the types it can have are constrained by the types of subexpressions/used functions. There is a minimal set of constraints you need to specify, e.g. l = [toEnum 0, 3] needs the constraints (Enum a) and (Num a) in the type signature l :: (Num a, Enum a) => [a] but you can use more or more restrictive constraints to specify a less general type than it could have, for example l :: (Enum a, Bounded a, Num a, Read a) => [a] or l :: (Enum a, RealFrac a) => [a] are valid signatures since they are less general/more constrained than the first one. You could also give a monomorphic signature, l :: [Double], which will compile if and only if the specified type of list elements belongs to Num and Enum.