While I was writing a small example of the Show instance, I had made an indentation error:
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=print B0
Where, clearly, the working code is:
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=print B0
I was expecting to get a compile error on the first one, but instead I could run it and it ended up in:
example.hs: stack overflow
Why does this code even compile?
Also, why is this a runtime error only (which, if stack is unconstrained, fills up your RAM) and not a compilation error?
The body of an instance
can be empty. You can leave out the where
clause:
instance Show B
but you can also include it:
instance Show B where
-- nothing here
This can be useful for type classes that provide default implementations for the methods, perhaps based on generic programming facilities. For example, with the aeson
package, the usual way of defining instances to convert to and from JSON is to use generics and empty instances:
{-# LANGUAGE DeriveGeneric #-}
import GHC.Generics
data Person = Person {
name :: Text
, age :: Int
} deriving (Generic, Show)
-- these empty instances use Generics to provide a default implementation
instance ToJSON Person
instance FromJSON Person
In your program, by leaving out the indentation, you've defined an instance Show B
with no method definitions (and -Wall
would generate a "missing method" warning telling you that it did not meet the minimal requirements for the instance). The unindented show
is providing a new top-level definition for show
, unrelated to the show
in the Show
type class.
You haven't used show
explicitly. Instead, you've used it implicitly via print
, which always calls the show
from the type class, ignoring your top-level definition, so your crashing program is equivalent to:
data B = B0 | B1
instance Show B
main = print B0
This generates a stack overflow because there are default definitions of show
and showsPrec
that are used when no specific instances are given:
show x = shows x ""
showsPrec _ x s = show x ++ s
which operate together with the top-level function shows
(not part of the type class):
shows = showsPrec 0
This works great when at least one of show
or showsPrec
is defined in the instance, and then the other gets a sensible definition, but if neither is defined, this creates an infinite recursive loop between these three functions.
Also, note that the following program would tell you show
was ambiguous which would make it clearer what was going on.
module Main where
data B= B0|B1
instance Show B where
show B0="0"
show B1="1"
main=putStrLn (show B0) -- instead of print
Since you did not indent show
, it means this does not belong to the instance
for Show
, you program is thus equivalent to:
instance Show B where -- ← no implementations
show B0 = "0"
show B1 = "1"
You here thus construct another function show
, that has nothing to do with the Show
typeclass, but simply has the same name.
Show
defines three functions showsPrec :: Show a => Int -> a -> String -> String
, show :: a -> String -> String
, and showList :: [a] -> String -> String
. showsPrec
is often used since it has a precedence system to use parenthesis.
As a result show
is impelemented in terms of showsPrec
and showsPrec
is implemented in terms of show
:
class Show a where {-# MINIMAL showsPrec | show #-} -- … showsPrec :: Int -> a -> ShowS -- … show :: a -> String -- … showList :: [a] -> ShowS showsPrec _ x s = show x ++ s show x = shows x "" showList ls s = showList__ shows ls s -- … shows :: (Show a) => a -> ShowS shows = showsPrec 0
It is thus sufficient to implement showsPrec
or show
, that is why there is a {-# MINIMUM showsPrec | show #-}
pragma at the top of the type class.
If you do not implement any of the two, then the compiler will raise a warning, and use thus the basic implementations, but that will lead to nothing, since show
will call showPrec
, which will then call show
, until eventually the stack is exhausted.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With