[Type classes and monads Bryan O'Sullivan **20080326063104] { addfile ./examples/ch16/SupplyClass.hs hunk ./en/ch06-library.xml 3 - + hunk ./en/ch16-monad-case.xml 501 + + + + Separating interface from implementation + + In the previous section, we saw how to hide the fact that + we're using a State monad to hold the state for our + Supply monad. + + Another important way to make code more modular involves + separating its interface&emdash;what the + code can do&emdash;from its + implementation&emdash;how it does + it. + + The standard random number generator in + System.Random is known to be quite slow. If we use + our randomsIO function to provide it with + random numbers, then our next action will + not perform well. + + One simple and effective way that we could deal with this is + to provide Supply with a better source of random + numbers. Let's set this idea aside, though, and consider an + alternative approach, one that is useful in many settings. We + will separate the actions we can perform with the monad from how + it works using a type class. + + &SupplyClass.hs:class; + + This type class bears careful inspection, since it uses + several unfamiliar Haskell language extensions. We will cover + each one in the sections that follow. + + + Multi-parameter type classes + + How should we read the snippet MonadSupply s + m in the type class? If we add parentheses, an + equivalent expression is (MonadSupply s) m, which + is a little clearer. In other words, given some type variable + m that is a Monad, we can make + it an instance of the type class MonadSupply s: + unlike a regular type class, this one has a + parameter. + + These type classes with parameters are the first of our + language extensions. As the extension allows a type class to + have more than one parameter, the name of this extension is + MultiParamTypeClasses. The parameter + s serves the same purpose as the + Supply type's parameter of the same name: it + represents the type of the values handed out by the + next function. + + Notice that we don't need to mention &bind; or &return; in + the definition of MonadSupply s, since the type + class's context requires that a MonadSupply s + must already be a Monad. + + + + Functional dependencies + + To revisit a snippet that we ignored earlier, | m + -  s is a functional + dependency, often called a + fundep. We can read the vertical bar + | as such that, and the arrow + -> as uniquely determines. Our + functional dependency establishes a + relationship between m + and s + + The availability of functional dependencies is governed by + the FunctionalDependencies language pragma. + + The purpose behind us declaring a relationship is to help + the type checker. Recall that a Haskell type checker is + essentially a theorem prover, and that it is convervative in + how it operates: it insists that its proofs must terminate. A + non-terminating proof results in the compiler either giving up + or getting stuck in an infinite loop. + + With our functional dependency, we are telling the type + checker that every time it sees some monad + m being used in the context of a + MonadSupply s, the type s is + the only acceptable type to use with it. If we were to omit + the functional dependency, the type checker would simply give + up with an error message. + + It's hard to picture what the relationship between + m and s really means, so + let's look at an instance of this type class. + + &SupplyClass.hs:instance; + + Here, the type variable m is replaced + by the type S.Supply s. Thanks to our functional + dependency, the type checker now knows that when it sees a + type S.Supply s, the type can be used as an + instance of the type class MonadSupply s. + + If we didn't have a functional dependency, the type + checker would not be able to figure out the relationship + between the type parameter of the class MonadSupply + s and that of the type Supply s, and it + would abort compilation with an error. The definition itself + would compile; the type error would not arise until the first + time we tried to use it. + + To strip away one final layer of abstraction, consider the + type S.Supply Int. Without a functional + dependency, we could declare this an instance of + MonadSupply s. However, if we tried to write + code using this instance, the compiler would not be able to + figure out that the type's Int parameter needs to + be the same as the type class's s + parameter, and it would report an error. + + Functional dependencies can be tricky to understand, and + once we move beyond simple uses, they often prove difficult to + work with in practice. Fortunately, the most frequent use of + functional dependencies is in situations as simple as ours, + where they cause little trouble. We will have more to say + about them in XXX. + + Insert forward reference to fundep material + here. + + + + Rounding out our module + + If we save our type class and instance in a source file + named SupplyClass.hs, we'll need to add + a module header such as the following. + + &SupplyClass.hs:module; + + The FlexibleInstances extension is necessary + so that the compiler will accept our instance declaration. + This extension relaxes the normal rules for writing instances + in some circumstances, in a way that still lets the compiler's + type checker guarantee that it will terminate. Our need for + FlexibleInstances here is caused by our use of + functional dependencies, but the details are unfortunately + beyond the scope of this book. + + Finally, notice that we're re-exporting the + runSupply and Supply names + from this module. It's perfectly legal to export a name from + one module even though it's defined in another. In our case, + it means that client code only needs to import the + SupplyClass module, without also importing the + Supply module. This reduces the number of + moving parts that a user of our code needs to + keep in mind. + hunk ./examples/ch16/SupplyClass.hs 1 +{-- snippet module --} +{-# LANGUAGE FlexibleInstances, FunctionalDependencies, + MultiParamTypeClasses #-} + +module SupplyClass + ( + MonadSupply(..) + , S.Supply + , S.runSupply + ) where +{-- /snippet module --} + +{-- snippet instance --} +import qualified Supply as S + +instance MonadSupply s (S.Supply s) where + next = S.next +{-- /snippet instance --} + +{-- snippet class --} +class (Monad m) => MonadSupply s m | m -> s where + next :: m (Maybe s) +{-- /snippet class --} }