[Refine overlapping instances discussion. Bryan O'Sullivan **20080503045409] { hunk ./en/ch07a-json-typeclass.xml 101 - encounter a few other language extensions at various points in - this book. + encounter a few other language extensions in this chapter, and + a handful more later in this book. hunk ./en/ch07a-json-typeclass.xml 104 + hunk ./en/ch07a-json-typeclass.xml 106 - - Living in an open world + + Living in an open world hunk ./en/ch07a-json-typeclass.xml 109 - Haskell's typeclasses are intentionally designed to let us - create new instances of a typeclass whenever we like. + Haskell's typeclasses are intentionally designed to let us + create new instances of a typeclass whenever we like. hunk ./en/ch07a-json-typeclass.xml 114 - We can add new instances anywhere; they are not confined - to the module where we define a typeclass. This feature of - the typeclass system is referred to as its open - world assumption. If we could say the - following are the only instances of this typeclass that can - exist, we could have a closed - world. + We can add new instances anywhere; they are not confined to + the module where we define a typeclass. This feature of the + typeclass system is referred to as its open world + assumption. If we could say the following + are the only instances of this typeclass that can + exist, we could have a closed + world. hunk ./en/ch07a-json-typeclass.xml 122 - One useful instance we'd like to be able to write is to - turn a list into a JSON array. We won't worry about - implementation details just yet, so let's use - undefined as the bodies of the instance's - methods. + One useful instance we'd like to be able to write is to turn + a list into a JSON array. We won't worry about implementation + details just yet, so let's use undefined as the + bodies of the instance's methods. hunk ./en/ch07a-json-typeclass.xml 129 - It would also be convenient if we could turn a list of - name/value pairs into a JSON object. + It would also be convenient if we could turn a list of + name/value pairs into a JSON object. hunk ./en/ch07a-json-typeclass.xml 134 - If we put these definitions into a source file and load - them into &ghci;, everything initially seems fine. + If we put these definitions into a source file and load them + into &ghci;, everything initially seems fine. hunk ./en/ch07a-json-typeclass.xml 139 - However, once we try to use the - list-of-pairs instance, we run into trouble. + However, once we try to use the + list-of-pairs instance, we run into trouble. hunk ./en/ch07a-json-typeclass.xml 144 - This problem of overlapping instances - is a consequence of Haskell's open world assumption. Here's a - simpler example that makes it clearer what's going on. + This problem of overlapping instances + is a consequence of Haskell's open world assumption. Here's a + simpler example that makes it clearer what's going on. hunk ./en/ch07a-json-typeclass.xml 150 - We have two instances of the typeclass - Borked for pairs: one for a pair of - Ints and another for a pair of anything else - that's Borked. + We have two instances of the typeclass Borked + for pairs: one for a pair of Ints and another for a + pair of anything else that's Borked. hunk ./en/ch07a-json-typeclass.xml 154 - Suppose that we want to bork a pair - of Int values. To do so, the compiler must - choose an instance to use. Because these instances are right - next to each other, it may seem that it could simply choose - the more specific instance. + Suppose that we want to bork a pair of + Int values. To do so, the compiler must choose an + instance to use. Because these instances are right next to each + other, it may seem that it could simply choose the more specific + instance. hunk ./en/ch07a-json-typeclass.xml 160 - As we've noted, though, the instances don't - have to be next to each other. They could - be in two separate modules, and the code that's trying to use - the Borked typeclass in a third. When a module - contains an instance, the instance is automatically exported - from the module, but not in a way that's easily - visible. + However, &GHC; is conservative by default, and insists that + there must be only one possible instance that it could use. It + will thus report an error if we try to use + bork. hunk ./en/ch07a-json-typeclass.xml 165 - We could thus easily not know that one of those modules - contained an instance for Borked. As a result, - we could accidentally and silently change the behaviour of our - third module simply by importing the second instance, and we - probably wouldn't notice until runtime. The rule against - overlapping instances rule ensures that the compiler issues an - error instead. - + + When do overlapping instances cause an error? + + As we mentioned earlier, we can scatter instances of a + typeclass across several modules. &GHC; does not complain + about the mere existence of overlapping instances. Instead, + it only complains when we try to use a method of the affected + typeclass, when it is forced to make a decision about which + instance to use. + hunk ./en/ch07a-json-typeclass.xml 179 - &GHC; supports a language extension, - OverlappingInstances, that chooses the most - specific among a set of overlapping instances when possible. - Although it can be convenient, it suffers from the problem we - outline above, and the rules that apply to its use are subtle - and complicated. + &GHC; supports a useful language extension, + OverlappingInstances: when there are multiple + overlapping instances to choose from, this extension causes + the compiler to pick the most specific one. + + We frequently use this extension together with + TypeSynonymInstances, so that we can provide an + instance for String and an instance for other + types of list. Here's an example. + + &SimpleClass.hs:Foo; + + With the OverlappingInstances extension + enabled, &GHC; will still reject code if it finds more than + one most specific instance. + + + When to use the OverlappingInstances extension + + Here's an important point: &GHC; treats + OverlappingInstances as affecting the + declaration of an instance, not a + location where we use the instance. In other words, when we + define an instance that we wish to allow to overlap with + another instance, we must enable the extension for the + module that contains the definition. When it compiles the + module, &GHC; will mark that instance as can be + overlapped with other instances. + + Once we import this module and use the instance, we + won't need to enable + OverlappingInstances in the importing module: + &GHC; will already know that the instance was marked as + okay to overlap when it was defined. + + This behaviour is useful when we are writing a library: + it lets us choose to create overlappable instances, without + forcing clients of our library to enable language extensions + in order to use it. + + + + + + How does show work for strings? + + Since the OverlappingInstances and + TypeSynonymInstances language extensions are + specific to &GHC;, how does the machinery around the familiar + Show typeclass render [Char] + differently from [Int]? Via a clever + trick. hunk ./en/ch07a-json-typeclass.xml 232 - As with all of &GHC;'s tuning knobs that loosen the - restrictions on the type checker, we recommend using this - extension only sparingly, and with great care. In the - sections that follow, we'll describe how to modify our code to - work without with extension. + The Show class defines both a + show method and a + showList method. The default + implementation of showList renders using + square brackets and commas. The instance of Show + for Char provides a special implementation of + showList that uses double quotes and + escapes non-ASCII-printable characters. addfile ./examples/ch07/SimpleClass.hs hunk ./examples/ch07/SimpleClass.hs 1 +{-- snippet Foo --} +{-# LANGUAGE TypeSynonymInstances, OverlappingInstances #-} + +import Data.List + +class Foo a where + foo :: a -> String + +instance Foo a => Foo [a] where + foo = concat . intersperse ", " . map foo + +instance Foo Char where + foo c = [c] + +instance Foo String where + foo = id +{-- /snippet Foo --} }