How to construct complex string

Hello all, recently I was working on a haskell program, which does some calculations and the produces a "plot", i.e. a set of Tikz (a LaTeX thing) commands. While this was not a problem in itself, it lacked composability. While all my plots show a grid, a caption and some data points, some of them additionally show a line with a legend, some of them show two. Now I clearly do not want to duplicate the entire function just to add another line. I'd rather construct the Tikz commands (which at the end of the day is just a String) in a stepwise fashion. When constructing the set of Tikz commands, I cannot rely that additional commands are placed at the end. It is typically more an inside-out thing. The last thing I need to do is wrap the whole thing in \begin and \end directives. Additionally there may be "global" values (like the scale) which need to be known at more than one step. I had a brief look at the "Diagrams" package, which MUST have similar issues, and they "do everything with monoids", but I fail to see the light. Could anyone point me in the right direction?

Hi Martin If you are building syntax as a sequence of commands (or statements) a common idiom is to collect them with a Writer monad. Andy Gill's Dotgen on Hackage is a nice realization of this idiom. See the how example included in the package has acceptably nice syntax without obliging the implementation to use complex facilities such as Template Haskell. In the source code, the data type GraphElement represents the main statement types in GraphViz's "dot" language. The data type Dot is the monad - here a combination of a Writer monad and a State monad, with the State monad supplying unique integers for fresh identifiers. The monad is written as a direct combination of State and Writer (it is perhaps more common to rely on _monad transformers_ but once you know what you are looking at, Andy's code is simple and clear). http://hackage.haskell.org/package/dotgen Best wishes Stephen

Am 08/06/2013 07:01 PM, schrieb Stephen Tetley:
Hi Martin
If you are building syntax as a sequence of commands (or statements) a common idiom is to collect them with a Writer monad.
Andy Gill's Dotgen on Hackage is a nice realization of this idiom. See the how example included in the package has acceptably nice syntax without obliging the implementation to use complex facilities such as Template Haskell.
Thanks Stephen, exactly the nudge I was looking for, except I could not find any examples in the package. I had a brief look at the source code and it did not instantly reveal itself. Actually I already got stuck here: data Dot a = Dot { unDot :: Int -> ([GraphElement],Int,a) } Thanks Brent for you detailed explanation.

Hi Martin The example is actually in the test directory - DotTest.hs.
data Dot a = Dot { unDot :: Int -> ([GraphElement],Int,a) }
This is the monad in which you build syntax - when the code is run it is pretty printed into a String. This particular monad is a combination of the Writer monad - it accumulates a list of GraphicElement and a state monad - Int is passed around and incremented so it can generate variable names: As separate pieces the monads are:
data State a = State { unState :: Int -> (Int, a) }
i.e. a function from state of type `Int` to a tuple of state and a polymorphic answer (Int,a).
data Writer a = Writer { unWriter :: ([GraphElement],a) }
i.e. a tuple of GraphElement's accumulated in a list and a polymorphic answer. Once you have a "declaration" of a monad you also have to provide an instance of the monad class so you can use the do-notation, in the Dotgen code the instance is: instance Monad Dot where return a = Dot $ \ uq -> ([],uq,a) m >>= k = Dot $ \ uq -> case unDot m uq of (g1,uq',r) -> case unDot (k r) uq' of (g2,uq2,r2) -> (g1 ++ g2,uq2,r2) This is a bit complicated as it is a combination of the monadic operation of both the State and Writer monad. With luck for just generating commands (and not generating fresh variables) you should be able to manage with only a Writer monad. There is one already in the monads package (mtl) in the Platform. The main part of the Dotgen library are the functions `edge`, `node`, (.->.) etc. which build Dot commands. In `edge` you can see a single command being built `[GraphEdge from to attrs]` in the first cell of the answer tuple: -- | 'edge' generates an edge between two 'NodeId's, with attributes. edge :: NodeId -> NodeId -> [(String,String)] -> Dot () edge from to attrs = Dot (\ uq -> ( [ GraphEdge from to attrs ],uq,())) Because no fresh variables are generated, the state `uq` (read as "unique") is returned unaltered in the answer tuple along with the void answer `()`. The other key part of the library is the so-called "run function" which evaluates monadic expressions - here the run function is called showDot: -- 'showDot' renders a dot graph as a 'String'. showDot :: Dot a -> String showDot (Dot dm) = case dm 0 of (elems,_,_) -> "digraph G {\n" ++ unlines (map showGraphElement elems) ++ "\n}\n" showDot unwraps `dm` from the Dot data type. As dm is a function ` Int -> ([GraphElement],Int,a) ` it needs to be applied to the initial state - here 0. This produces the answer 3-tuple, where we are only interested in the accumulated list of dot commands (the first cell). This list of commands is pretty printed into legal Dot syntax. Hope this helps somewhat.

Am 08/09/2013 12:18 AM, schrieb Stephen Tetley:
Hi Martin
instance Monad Dot where return a = Dot $ \ uq -> ([],uq,a) m >>= k = Dot $ \ uq -> case unDot m uq of (g1,uq',r) -> case unDot (k r) uq' of (g2,uq2,r2) -> (g1 ++ g2,uq2,r2)
Thanks Stephen I have a related question to the one above: The "case .. of" is just required to bind the variables left of the "->", as there is only one alternative, right? I tried to rewrite this using "where" and was bitten badly, because inside the "where" uq would not be defined. I found a way to write (a simplified version of) this using "let .. in", but I had to put the entire "let .. in" after the "->" in the lambda and it ended up equally ugly. So my question is: is "case .. of" the the "where" of lambdas? Are there alternatives? I find this confusing because "case .. of" looks like a decision between alternatives, rather than a way to bind variables.

Hi Martin I think case ... of here is equivalent to let ... in the only difference is that it can avoid growing rightwards. The declaration and body of let would have to be indented to the same level as the `let` keyword. With case ... of the consequent expression doesn't have to be indented to the level of `case` provided it is on a new line. I don't think I've ever noticed this "typographical trick" of a single case _case_ in other code.

Am 08/09/2013 08:27 PM, schrieb Stephen Tetley:
I don't think I've ever noticed this "typographical trick" of a single case _case_ in other code.
Are there other ways of doing this? What do you guys do, when you are tempted to write "where" but you are behind the "->" of a lambda and you want to refer to the arguments?

On Sat, Aug 10, 2013 at 12:41 AM, martin
instance Monad Dot where return a = Dot $ \ uq -> ([],uq,a) m >>= k = Dot $ \ uq -> case unDot m uq of (g1,uq',r) -> case unDot (k r) uq' of (g2,uq2,r2) -> (g1 ++ g2,uq2,r2)
I tried to rewrite this using "where" and was bitten badly, because inside the "where" uq would not be defined.
You could write: m >>= k = Dot f where f uq = (g1 ++ g2, uq2, r2) where (g1, uq', r) = unDot m uq (g2, uq2, r2) = unDot (k r) uq' Doubtless, there has to be a way of revealing the inner structure, which looks like some state monad with icing on top. -- Kim-Ee

On Mon, Aug 05, 2013 at 09:37:18PM +0200, martin wrote:
Hello all,
recently I was working on a haskell program, which does some calculations and the produces a "plot", i.e. a set of Tikz (a LaTeX thing) commands.
While this was not a problem in itself, it lacked composability. While all my plots show a grid, a caption and some data points, some of them additionally show a line with a legend, some of them show two.
Now I clearly do not want to duplicate the entire function just to add another line. I'd rather construct the Tikz commands (which at the end of the day is just a String) in a stepwise fashion.
When constructing the set of Tikz commands, I cannot rely that additional commands are placed at the end. It is typically more an inside-out thing. The last thing I need to do is wrap the whole thing in \begin and \end directives.
Hi Martin, I think the key thing you are missing is that a String is just a particular concrete representation of a TikZ program. That is not what a TikZ program really *is*. Instead, TikZ programs are trees. String is nice for storing a program in a file, but it is a terrible representation to use when working with programs, whether generating them, type-checking them, or whatever. As you have discovered, when working with Strings you have to insert stuff at various arbitrary places (at the beginning, the end, the middle), which is quite annoying and difficult (especially the middle!), whereas those same operations are simple on a tree representation of the program. To do what you are trying to do in a nice way would require (1) making an algebraic data type to represent the structure of TikZ programs (2) changing your program so that it generates values of this type, instead of Strings, and (3) making a single function to convert a value of this type into a String. This can be quite a bit of work up front, but well worth it if you are going to do more than just a bit of TikZ generation. With that said, however, did you know there is a TikZ backend for diagrams? [1] It should be possible for you to describe your plots using diagrams and then output them to TikZ (or any other format for which diagrams has a backend---SVG, PDF, PS, or PNG). I have not looked at it recently but would be happy to support it if you run into any problems (I will probably want it myself sooner or later). [1] http://hackage.haskell.org/package/diagrams%2Dtikz -Brent
Additionally there may be "global" values (like the scale) which need to be known at more than one step. I had a brief look at the "Diagrams" package, which MUST have similar issues, and they "do everything with monoids", but I fail to see the light.
Could anyone point me in the right direction?
_______________________________________________ Beginners mailing list Beginners@haskell.org http://www.haskell.org/mailman/listinfo/beginners
participants (4)
-
Brent Yorgey
-
Kim-Ee Yeoh
-
martin
-
Stephen Tetley