In the previous part, we ended with the following types:
> data OurSite > = HomePage > | MyGallery Gallery > | YourGallery Gallery > deriving (Read, Show) > data Gallery > = Thumbnails > | ShowImage Int Size > deriving (Read, Show) > data Size > = Full > | Screen > deriving (Read, Show)The problems we faced were:
showLink
was only called using
data-types that were actually handled by the sitegallery
module in a larger sitegallery
don't
collide.To solve the first problem, we need to restrict the types that
showLink
can be applied to. To solve the second two
problems, we need some way to record the current context so that we
can use it when we generate the Link
.
The perfect tool for this job is the Reader monad (also known as the Enviroment monad):
> type Link = String > type LinkM link a = Reader (link -> Link) aWe store a function in the enviroment which can be used to turn a
data type into a Link
. The showLink
function is now parameterized over this monad as follows:
Now showLink
can only be called on types supported by the
current environment. As a practical example, our gallery
function will now looks like this:
So, now if we tried to call showLink True
inside gallery
we would get an error like:
/home/stepcut/n-heptane/projects/haskell/urlt/tutorial/SimpleSite2.lhs:81:26: Couldn't match expected type `Gallery' against inferred type `Bool' In the first argument of `showLink', namely `True' In a 'do' expression: img1 <- showLink True In the expression: do img1 <- showLink True return $ pageTemplate ((toHtml $ "Showing " ++ username ++ "'s gallery thumbnails.") +++ br +++ (anchor (toHtml "image 1") ! [href img1])) Failed, modules loaded: none.
The LinkM monad also provides us with a convenient way to embed
the gallery
library into a larger site:
Here is how we would use nestLink
to support OurSite
Note that in the alternative for HomePage
we create
the links to the galleries two different ways. But in both instances
the type of the link is OurSite
.
More interesting is the alternatives which match on
MyGallery
and YourGallery
. To nest the
gallery
we just use nestLink MyGallery
and
nestLink YourGallery
. The gallery
library
itself needs no additional adjustments.
Additionally, let's say we forgegt the nestLink in the last alternative and instead write:
ourSite (YourGallery g) = gallery "someone else" g
At compile time we will get an error like:
/home/stepcut/n-heptane/projects/haskell/urlt/tutorial/SimpleSite2.lhs:134:6: Couldn't match expected type `OurSite' against inferred type `Gallery' Expected type: Reader (OurSite -> Link) Html Inferred type: LinkM Gallery Html In the expression: gallery "someone else" g In the definition of `ourSite': ourSite (YourGallery g) = gallery "someone else" g Failed, modules loaded: none.
With the addition of the LinkM monad, we have neatly addressed the goals set forth. We have leveraged the type-checker to ensure that all the internal links are associated with some code that handles them. Additionally, we can import third-party modules into a larger site, and the generate links are automatically adjusted to match the site structure. We can use the same 3rd-party module in more than one place and the links will not collide.
The implementation presented in this part is a bit limiting, because we can not use the IO monad or other similar Monads our site function. In the next part we will make some trivial modifications to use the Reader Monad Transformer instead of the plain Reader Monad.
The Link
s generated by this code are very scary
looking. In the next part, we would show how to make prettier looking
links.
The type-safety of the Links is in part due to the fact that the argument to the handler function is the same as the type of link the monad is parameterized over. For example:
ourSite :: OurSite -> LinkM OurSite Html
One way to enforce this required is to code it into the type which represents our site:
> > data Site link a > = Site { handleLink :: link -> LinkM link a > , defaultPage :: link > , formatLink :: link -> Link > , parseLink :: Link -> Maybe link > } > ourSiteSpec :: Site OurSite Html > ourSiteSpec = > Site { handleLink = ourSite > , defaultPage = HomePage > , formatLink = escapeURIString isUnescapedInURI . show > , parseLink = readLink > }Alternatively, we might choose to encode it in the LinkM type directly:
> type LinkM' link a = link -> Reader (link -> Link) aHowever, I find that confusing. Consider:
> testFunc :: String -> LinkM' link link > testFunc str lnk = return lnkThe type appears to indicate that testFunc takes one argument instead of two.
Also, we can not write the type signature for nestLink using the
LinkM'
type synonym. For these reasons I prefer the
first option.
In part III, we will make some small, final adjustments to make the library more usuable for real world projects