Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Why am I forced to specify a type here?

I have the following code:

class ToString a where
    toString :: a -> String

instance ToString String where
    toString a = a

instance ToString Char where
    toString a = [a]

instance ToString Int where
    toString a = show a

instance ToString Integer where
    toString a = show a

instance ToString Float where
    toString a = show a

instance ToString Double where
    toString a = show a

I can do toString "Text" and toString 't' and both compile fine. But if I do toString 5 I get and error. I am forced to do toString (5::Int).

show does not need a type specified to work. And when I look at the implementations of Show, I don't see anything magical:

instance Show Int where ...

instance Show Integer where ...

What am I doing wrong that requires me to specify the type and how can I fix it?

UPDATE:

I added {-# LANGUAGE ExtendedDefaultRules #-} as suggested below, and it worked perfectly. Solved my problem.

like image 355
GreenSaguaro Avatar asked Dec 11 '22 15:12

GreenSaguaro


2 Answers

You are required to specify a type because 5 is polymorphic in Haskell:

λ> :type 5
5 :: Num a => a

so the compiler doesn't know which Num instance to choose. However, because of extended defaulting, this does work in ghci:

λ> toString 5
"5"
like image 193
Rein Henrichs Avatar answered Dec 29 '22 23:12

Rein Henrichs


When you write toString "Text" or toString 't', Haskell is able to tell exactly which concrete type "Text" and 't' are: [Char] (aka String) and Char respectively. So it can select an instance and run your code.

toString 5 is subtly different. Numeric literals like 5 are overloaded in Haskell, so that 5 could be an Int, a Double, or something completely different with a Num instance that doesn't have a ToString instance. So it's not specified which instance of ToString we should be using (and note that you'll get different observable behaviour with your existing instances based on whether you choose Int or Double). Haskell's general response to such a situation is to to report an ambiguous type error, requiring you to do something to pin down the types more.

The authors of the Haskell spec considered that this would be a very common and annoying problem with numeric types in particular, so they built in a mechanism for defaulting ambiguous types, under certain conditions that are fairly conservatively designed to help with numeric literals used with standard Haskell features only.1

A type will only be defaulted if both:

  1. All the constraints on it only involve built-in type classes
  2. At least one of the classes is numeric (Num, Floating, etc)

So show 5 works because the constraints on the type of 5 are (Num a, Show a); Num is a numeric class, and all of the constraints are built-in classes.

toString 5 seems like it should be basically the same, but you get the constraints (Num a, ToString a), and since ToString isn't a built-in class defaulting doesn't apply, leaving you with an ambiguous type error.

The extended defaulting rules used in GHCi (or if you use the ExtendedDefaultRules extension) relax the "only built-in classes" rule (among other extensions), which makes (Num a, ToString a) eligible for defaulting after all.

The GHC user manual talks about how the usual type defaulting rules are extended in GHCi (or with the ExtendedDefaultRules extension), comparing them to the standard rules:

https://downloads.haskell.org/~ghc/latest/docs/html/users_guide/ghci.html#type-defaulting-in-ghci


1 In practice I find it to be an extremely rare problem in actual development (I usually compile with a flag that warns about type defaulting), but that's because I adopt the (common) practice of giving type signatures to almost all top-level functions. When GHC is invoked to compile entire modules written with that practice, there's almost always enough information available to specify all the types and defaulting is never needed.

For example, toString x would be perfectly fine if it occurred in a function where x was an argument with type Int. It would even be fine if x was a local variable with no explicit type, but it was also passed to a function that required an Int argument. Or was put in a list along with something else that was known to be an Int. Etc etc.

But in GHCi you supply expressions to be analysed one-at-a-time, and usually don't bother with optional things like type signatures. Under these conditions numeric literals (and other expressions) are ambiguously typed much more frequently, which is the motivation for GHCi's extended default rules.

like image 21
Ben Avatar answered Dec 29 '22 21:12

Ben