Since a time ago, I wanted to add trees to HaTeX. Some way to, given a Haskell tree, create a LaTeX output according to it. So I created the datatype:
data Tree a =
Leaf a
| Node (Maybe a) [Tree a]
Leaf a
| Node (Maybe a) [Tree a]
and started thinking about what LaTeX package I should to use in order to drawing trees. Since there are several good options, I decided to keep the Tree datatype in a separated module and write different implementations in different modules with similar interfaces. Then, I started with the qtree package and, in a few minutes, I had an example working. So I was happy for the moment.
The problem
But my happiness did not last long. The method used to transform a Haskell tree into a LaTeX value was to have a function that creates a LaTeX value from each node and, then, build the tree following the LaTeX tree syntax. So, the type of the function, called tree, was:
tree :: (a -> LaTeX) -> Tree a -> LaTeX
And this worked pretty well. The problem came out when I wanted to run metahatex in order to create the analogous monadic version. The modus operandi of metahatex is to read the type of the functions and infer from it their monadic implementation, re-using the original implementation. For example, if we have:
foo :: LaTeX -> a -> LaTeX
then, metahatex (importing the former qualified as App) do:
foo :: Monad m => LaTeXT_ m -> a -> LaTeXT_ m
foo lm a = do
l <- extractLaTeX_ lm
textell $ App.foo l a
where extractLaTeX_ gets the LaTeX value produced by the LaTeXT monad and textell puts LaTeX values again in the monad (like the tell method of the writer monad).
This method has worked perfectly until now. But, what happens if we try to apply it to the tree function? As we needed to transform a value of type LaTeX to another of type LaTeXT_ m for foo, we will need to do so from a a -> LaTeXT_ m typed value to a a -> LaTeX typed value. And that is impossible!
Searching a solution
I never liked the idea of write the monadic code manually, that would be write duplicated code. I went then to eat a pizza and think about it. Typeclasses came to my mind. When I returned to my computer, I started to search what minimal functions I need to render the tree. Then, I wrote a typeclass and made LaTeX and LaTeXT_ instances of it. See the definition of the resulting typeclass:
class (Monoid l, IsString l) => LaTeXTree l where
texbraces :: l -> l
texcomms :: String -> l
totex :: Render a => a -> l
The first and second method are abstractions of the TeXBraces and TeXCommS type constructors! And the other is the abstraction of the rendertex function! Making LaTeX and LaTeXT instances of this typeclass allow us to construct a tree function valid to both types. But this is not the end. The same idea is applicable to the whole library, so normal and .Monad modules can be merged using a typeclass with abstractions of all LaTeX constructors!
Conclusion
Well, this idea had come to me a time ago, but I just realized today how useful it can be. And now, I feel a bit odd taking this approach only to trees. What should I do?
3 comments:
Hi!
Personnally, I like the clean API of the non-monadic modules of HaTeX. I would prefer to keep them like this and not introduce class constraints everywhere. (But I didn't really think about the monadic versions yet. Why are they needed?)
BTW, thanks for HaTeX, it's really useful and nicely done!
Sönke
Hi, Sönke!
Thanks for your feedback. I also like a clean interface, but a class constraint won't change that. Furthermore, there will NOT imply code breaking, since LaTeX datatype will be an instance of the class (am I missing something?).
The point of monadic versions is to write LaTeX code with do notation, like this:
aPreamble = do
documentclass [] article
usepackage [utf8] inputenc
author "An author"
title "A title"
Also, it allows you to do some other computation, like read a file, while writing your LaTeX code (although this practice can be seen as a bad idea for some people):
foo2 = lift (readFile "yourFile") >>= verbatim . fromString
I usually work importing monadic modules because I find the do notation more comfortable for this task.
Anyway, I guess is just a matter of taste.
Hi Daniel!
Won't all the functions with type 'Latex -> Latex' change to something like 'LaTeXTree l => l -> l'? That's the additional clutter in the API I was thinking of.
I see the appeal of using the do-notation for this, but I think it's kind of a mis-use: What we are doing with HaTeX is to write down sequences of LaTeX-snippets. So I think (<>) is very appropriate. Monads are Monoids with (>>) being equivalent to (<>), but using the do-notation to write down sequences feels kind of wrong to me. I can't think of a situation where I would make use of return or (>>=).
(While your example where you use (>>=) doesn't really look that bad to me, I can't imagine a situation, where I really would want to do something like this.)
But you're in good company (with using the do-notation for non-monadic stuff): IIRC the shake library by Neil Mitchell does this (and there are probably a dozen other examples). So, I completely agree: it's a matter of taste.
Cheers,
Sönke
Post a Comment