The module Type.hs
defines the homonym newtype
and exports only its type constructor, but not the value constructor, to avoid exposing the detail; it also provides a constructor function makeType
to balance the lack of value ctor. Why do I need to wrap a String
in a new type? Because I want it to be more than a String
; in my specific case, Type
is actually called Line
, and what corresponds to makeType
enforces that it contains only one \n
, as the last character. A newtype
seemed the most obvious choice to me. If this is not the case, forgive me: I'm learning.
module Type (Type, makeType) where
newtype Type = Type String
makeType :: String -> Type
makeType = Type
In order to show a value of type Type
the way I like (for instance, given my actual usecase of Line
, I might want to represent \n
with a nice unicode character, or with the sequence <NL>
, or whatever), I created another module TypeShow.hs
, which I later (in the attempt of doing what I'm describing) I edited by adding some pragmas. Why another module? Because I guess the way something works inside and the way I show it to screen are two separete aspects. Am I wrong?
{-# LANGUAGE FlexibleInstances #-}
module TypeShow () where
import Type
instance Show Type where
show = const "Type"
-- the following instance came later, see below why
instance {-# OVERLAPS #-} Show (Maybe Type) where
show (Just t) = show t
show _ = ""
Beside this pair of modules (which describe the core of Type
, and how it should be shown), I created other similar pairs Type1
/Type1Show
, Type2
/Type2Show
, which all wrap a String
to represent and show other String
-like entities.
For other reasons, I also needed another type which wraps an optional value, which can be of type Type
, Type1
, or any other type, so I wrote this module
module Wrapper (Wrapper, makeWrapper, getInside) where
newtype Wrapper a = Wrapper { getInside :: Maybe a }
makeWrapper :: a -> Wrapper a
makeWrapper = Wrapper . Just
(In reality Wrapper
actually wraps more than one Type
value, but I'll avoid putting more details then necessary; if the following is stupid exactly because I'm wrapping only one Type
value in Wrapper
, then please consider it's wrapping more than one, in reality.) Again, here I tried to hide the details of Wrapper
while providing makeWrapper
to make one, and getInside
to have a "controlled" access to its inside.
I also wanted to show this on screen, so I created a corresponding WrapperShow.hs
module, so that Wrapper
's show
method relies on the content's show
method.
module WrapperShow () where
import Wrapper
instance Show a => Show (Wrapper a) where
show = show . getInside
At this point, however, when the type a
is a Maybe Type
, I wanted to show the content of the Wrapper
printing an empty string instead of Nothing
, or the content of the Just
; therefore I wrote the instance Show (Maybe Type)
that I commented above.
Given this, Type "hello"
and Just $ Type "hello"
are both correcly shown as Type
, but Wrapper $ Just $ Type "hello"
is displayed as Just Type
, just like it's using Maybe
's original instance of Show
, irrespective of the fact that for this specific type inside the Maybe
(the Type
) I've customized the Show
instance.
In the
Show
instance declaration forWrapper
, we don't really know whata
is. But apparently we must already choose whatShow
instance to use for theMaybe a
. With the information available, the only instance that matches is the default oneShow a => Show (Maybe a)
, which doesn't require a concretea
.The GHC User Guide, in the section about overlapping instances, mentions the concept of "postponing" the choice of an instance:
and
We could try such a trick. Instead of requiring
Show a
, require the wholeShow (Maybe a)
. Now theShow
instance is taken as "given" and we don't make a local decision. I think this has the effect of delaying the selection of theShow (Maybe a)
instance to call sites likeprint $ Wrapper $ Just $ Type 3
, where we have more information about the concrete type ofa
.Testing this hypothesis:
That said, I find this behavior confusing and would steer clear of it in production code.
As @dfeuer notes in a comment and linked code, the overlapping instance is problematic. For example, if we add this innocent function to the code of this answer:
The module ceases to compile:
But now I'm confused. Why doesn't the exact same type error happen with the instance definition
Show (Wrapper a)
in the original question?The reason
foo
fails to compile seems to be the last bullet point in the description of the instance search procedure:Perhaps—unlike normal functions—instance definitions are exempt from this particular rule, but I don't see that mentioned in the docs.