tag:blogger.com,1999:blog-88899279298948393182014-10-02T21:52:03.690-07:00equinoxHaskell for Web 2.0Sashahttp://www.blogger.com/profile/07382747257624276619noreply@blogger.comBlogger3125tag:blogger.com,1999:blog-8889927929894839318.post-54899401597666854562007-06-26T16:34:00.000-07:002008-10-16T01:19:34.119-07:00Search in HaskellI've been playing around today with search in Haskell, and besides from some overly technical papers and a confusing explanation on the YAHT wiki book, I haven't found anything useful. So I though I'd put up what I could figure out.<br /><br />First, let's use type classes to get some modularity. Search can be over trees or graphs or whatever, basically we just need states and a function to give us the children of a node. We also are going need to compare states to check if we have reached the goal.<br /><br />> class (Ord a) => SearchState a where<br />> children :: a -> [a]<br /><br />For instance, let's say we are trying to solve the numeric maze given in the <a href="http://www.haskell.org/haskellwiki/Haskell_Quiz/Numeric_Maze">haskell wiki.</a> In this problem our state is just an integer.<br /><br />> newtype NumMaze = NumMaze Integer deriving (Show, Eq, Ord)<br /><br />And here is the instantiation. Our operations to generate children are (x+2), (x*2), (x/2). So<br /><br />> instance SearchState NumMaze where<br />> children (NumMaze n) =<br />> map NumMaze $<br />> [n+2, n*2]++[n `div` 2 | 2 `divides` n]<br />> where divides a b = b `mod` a == 0<br /><br />Now we can write very generic functions for the search operations. We'll start with a monadic depth first search. Depth first search is very natural for functional programming (if we don't worry about blowing the stack). We dive in with recursion. There is good explanation of this on the haskell wikibook <a href="http://en.wikibooks.org/wiki/Haskell/YAHT/Monads">here</a>. Instead of using a graph, we use our search state and simplify the code by using a fold.<br /><br />> dfs :: (SearchState a,MonadPlus m) => a -> a -> m[a]<br />> dfs start target<br />> | start == target = return [start]<br />> | otherwise =<br />> foldr mplus mzero $<br />> [ dfs c target >>= \path -> return (start:path)) |<br />> c <- children start] The code has two cases. If we have reached the goal, it returns a list of success with the result. Otherwise, in the list comprehension it tries searching each child. If it finds a successful path, it adds it returns it. You might wonder where we handle failure. If there are no children, the fold returns mzero which will propagate failure to its parents. When the parent gets a failure, mplus will try a different path, and we have backtracking. Next we'll try breadth first search. Here is one way to do it by passing a queue as an accumulator. > bfs :: (SearchState a,Monad m) => a -> a -> m [a]<br />> bfs start target = bfs' [[start]] >>= return . reverse<br />> where<br />> bfs' [] = fail "No Path"<br />> bfs' ((s:r):ns)<br />> | s == target = return (s:r)<br />> | otherwise = bfs' (ns ++[c:s:r |c<-children s]) We can make this faster by adding a set to keep track of states that we have already visited. > import Data.Set (empty, notMember, union, fromList) <br /><br />> bfs2 :: (SearchState a,Monad m) => a -> a -> m [a]<br />> bfs2 start target = bfs' [[start]] empty >>= return .reverse<br />> where<br />> bfs' [] _ = fail "No Path"<br />> bfs' ((s:r):ns) seen<br />> | s == target = return (s:r)<br />> | otherwise = bfs' (ns ++new') seen'<br />> where<br />> new' = [c:s:r|<br />> c <- children s, > c `notMember` seen]<br />> seen' = seen `union` (fromList $ map head new')<br /><br />So these functions are nice, and they are how I would write them in O'Caml. But they just feel too ugly for haskell. I know, I'm getting spoiled. So I thought I would give a shot at a lazier version. The rest of this entry is my very basic understanding of the Functional Pearl <a href="http://www.google.com/url?sa=t&ct=res&cd=2&url=http%3A%2F%2Fspivey.oriel.ox.ac.uk%2F%7Emike%2Fbfs%2Fbfs.ps&ei=_72BRqiEG4LQecGDjbYK&usg=AFQjCNGUBM60AG1iwWZq3GRNfKkFMLizKg&sig2=BIX3joEA4fAbmvxEXDysxg">Breadth-First Combinators</a>.<br /><br />First, depth first search. I visualize it like this. Imagine, that the states form a tree, and we want to first transform the tree to a stream, and then process it linearly. Haskell makes this absurdly easy. First, imagine that we can linearize all the children searches. This gives us a matrix-like list of streams (since we are doing DFS, we hope they are not infinite, but that does not break the formulation) i.e.<br /><br />[[Child1, Child1.1, Child1.2 ...],<br />[Child2, Child2.1, ...],<br /> ...<br />]<br /><br />Since this is DFS, we want to explore these in order. This means all we need to do in concat them, and we have the full traversal. Bam!<br /><br />> lazyDFT :: (SearchState a) => a -> [a]<br />> lazyDFT start = start : (concat $ map lazyDFT $ children start)<br /><br />In the paper they call this the "and" operator for logical combination.<br /><br />Now, what about Breadth-first search. Well we want to move one level at a time instead of following a single path to its end. If we know that our tree is balanced, we can simply transpose the matrix and then take the concat<br /><br />[[Child1, Child1.1, Child1.2 ...],<br />[Child2, Child2.1, ...],<br /> ...<br />]<br />--><br />[[Child1, Child2, Child3 ...],<br /> [Child1.1, Child2.1, ...],<br /> ...<br />]<br /><br />Here is the code using Data.List's lazy transpose function.<br /><br />> lazyBFTBal :: (SearchState a) => a -> [a]<br />> lazyBFTBal start =<br />> start:(concat $ transpose $ map lazyBFTBal $ children start)<br /><br />Hmm... Not sure the best way to proceed here. In all honesty, I find the rest of the combinators paper to be a bit confusing to say the least, but I'll try to fake it. I want to make sure I traverse the tree in order, so I'll preserve the order at each level. The new type signature is .<br /><br />lazyBFT :: (SearchState a) => a -> [[a]]<br /><br />and the matrix looks like,<br /><br />[[[Child1], [Child1.1, Child1.2] ...],<br /> [[Child2], [Child2.1], [Child2.1.1]],<br /> ...<br />]<br />transposed to<br />[[[Child1], [Child2] ...],<br /> [[Child1.1, Child1.2], [Child2.1],...],<br />[ [Child2.1.1],..]<br /> ...<br /> ]<br /><br />and then we concat each row,<br /><br />[[Child1,Child2,...], [Child1.1,Child1.2],[Child2.1.1]]<br /><br />Score! Back to where we started.<br /><br />The code looks like,<br /><br />> lazyBFT start = [start]:(map concat $ transpose $ map lazyBFT $ children start)<br /><br />If I use it on the problem we started with yields.<br /><br />*Main> take 3 $ lazyBFT (NumMaze 10)<br />[[NumMaze 10],[NumMaze 12,NumMaze 5,NumMaze 20],[NumMaze 14,NumMaze 6,NumMaze 24,NumMaze 7,NumMaze 10,NumMaze 22,NumMaze 10,NumMaze 40]]<br /><br />Now lets add some of the pruning back.<br /><br />> lazyBFT2 seen start = [start]:(map concat $ transpose $ map (lazyBFT2 seen') $ c)<br />> where c = [c| c <- children start , c `notMember` seen] > seen' = seen `union` (fromList c)<br /><br />*Main> take 3 $ lazyBFT2 (singleton $ NumMaze 10) (NumMaze 10)<br />[[NumMaze 10],[NumMaze 12,NumMaze 5,NumMaze 20],[NumMaze 14,NumMaze 6,NumMaze 24,NumMaze 7,NumMaze 22,NumMaze 40]]<br /><br /><br /><br />Okay, I'll stop here for now. Pretty cool stuff. I wonder, is there is a lazy way to do this pruning? Maybe next post...Sashahttp://www.blogger.com/profile/07382747257624276619noreply@blogger.com39tag:blogger.com,1999:blog-8889927929894839318.post-32748548726898997392007-06-15T21:40:00.000-07:002007-06-15T22:57:52.158-07:00JSON-StaticSo I've been getting ready for work by playing with the Facebook web API. I got fed up pretty quickly with the xml interface, so I started using the json. I really like the simplicity of json, particularly for serializing types. I started working on this project in O'Caml, and I was really impressed by Martin Jambon's JSON libraries for O'Caml, <a href="http://martin.jambon.free.fr/json-static.html">json-static </a>. You can just throw in the keyword "json" and it write all the boilerplate code for the serialization. i.e.<br /><br />type json greeting = Hello | Goodbye<br /><br />will write code for greeting_of_json and json_of_greeting that do the default conversion, turning Hello to "Hello" and Goodbye to "Goodbye". This is super cool. But, what if you have some weird data type, say<br /><br />type json weird = {visible: int ; invisible:int}<br /><br />and you don't want to serialize the invisible type. hmm. Well we can define a special module.<br /><br />module Weird=<br />struct<br /> type t = {visible: int ; invisible:int}<br /> let of_json = ...<br /> let to_json = ...<br />end<br /><br />This is nice and all, but ... it is a real hack. O'Caml doesn't have this kind of polymorphism and this only works because of camlp4 magic.<br /><br />However, this is exactly the kind of thing Haskell can do. So in this entry, I'll try to port this idea to Haskell in a neat way. First, we start with a JSON parser. I'm using the one from <a href="http://www.tom.sfc.keio.ac.jp/%7Esakai/d/data/200604/JSON.hs">JSON.hs</a>. This library uses parsec to do the conversion from strings to a JSON data type and from the type back to pretty-printed strings. All we need to do is auto-generate the boilerplate code to convert from the json type to other types.<br /><br />So let's go. First we define the basic type class.<br /><br /><br />>module Data.Json where<br />>import qualified JSON as J<br /><br />The Json class has two basic functions. toJson converts data types to a JSON data structure.<br />fromJson tries to convert a JSON type into a Haskell type. The two list functions are defined<br />so that we can handle the String newtype. The default implementations should handle these functions in the other cases. <br /><br />>class Json a where<br />> toJson :: a -> J.Value<br />> fromJson :: (Monad m) => J.Value -> m a<br /><br />These follow the list functions from the prelude.<br /><br />> listToJson :: [a] -> J.Value<br />> listToJson = J.Array . map toJson<br />> listFromJson :: (Monad m) => J.Value -> m [a]<br />> listFromJson (J.Array a) = mapM fromJson a<br /><br />Now we define a couple instances for the basic data types.<br /><br />Some are really basic.<br /><br />>instance Json Double where<br />> toJson = J.Number<br />> fromJson (J.Number a) = return a<br /><br />A String is just a list of characters.<br /><br />>instance Json Char where<br />> toJson = J.String . show<br />> fromJson (J.String a) = return $ head a<br />> listToJson = J.String<br />> listFromJson (J.String a) = return a<br /><br />Lists just call the default list constructors.<br /><br />>instance Json a => Json [a] where<br />> toJson = listToJson<br />> fromJson = listFromJson<br /><br /><br />This part was the basic stuff. Now we need to derive instances for arbitrary types. It took me forever to figure out the best way to do this. I started with DRiFT, but I found the interface kind of duct-tapey. Next, I tried SYB, which seemed really neat and perfect for this task. Instead of generating code, it allows you to use folds over the types themselves. The one problem with SYB though is that it is really difficult to incorporate type classes. <a href="http://www.cwi.nl/%7Eralf/syb3/">SYB 3</a> shows you how to do this, but the implementation is not quite there yet.<br /><br />So I settled on Data.Derive with Template Haskell. Data.Derive provides a bunch of helper methods for writing derive instances for types, and a ton of ways of inserting the derived instances back into the code. So without further ado, here is the code.<br /><br />{-#OPTIONS_GHC -fth #-}<br />>module Data.Derive.Json(makeJson ) where<br /><br />This is the Data.Derive library<br /><br />>import Language.Haskell.TH.All<br />>import Language.Haskell.TH.Lib<br />>import Data.List<br />>import Control.Monad (liftM)<br />>import qualified Data.Map as M<br />>import qualified JSON as JS<br />>import qualified Data.Json as DJ<br /><br /><br /><br />>makeJson :: Derivation<br />>makeJson = derivationQ json "Json"<br /><br />For each derived element, we define a toJson and fromJson function.<br /><br />>json :: Dec-> Q [Dec]<br />>json dat =<br />> do<br />> toClauses <- toJson<br />> fromJ <- fromJson dat<br />> return $ simple_instance "Json" dat<br />> [funN "toJson" toClauses,<br />> funN "fromJson" fromJ]<br />> where<br />> toJson = sequence [sclause [ctp ctr 'x'] `liftM` jsonit ctr |<br />> ctr <-dataCtors dat]<br /><br />>litQ :: String -> ExpQ<br />>litQ = return . lit<br /><br /><br />This is the toJson code generator function. It generates code like this -<br /><br />data Test = RecordName {field1::String, field2::Integer}<br /><br />toJson (RecordName x1 x2) =<br /> JS.Object $ M.fromList $ [("field1",toJson x1),("field2",toJson x2)]<br /><br /><br />>jsonit:: Con -> ExpQ<br />>jsonit ctr =<br />> case ctorFields ctr of<br />> [] -> [|JS.String $(litQ $ ctorName ctr)|]<br />> fl -> flds fl<br />> where<br />> flds :: [String] -> ExpQ<br />> flds f = [|JS.Object $ M.fromList $<br />> $( liftM lst $ mapM field $ zip [1..] f)|]<br />> field :: (Int,String) -> ExpQ<br />> field (n,f) = [| ( $(litQ f),<br />> DJ.toJson $(return $ vrn 'x' n)) |]<br /> <br />This code looks a bit confusing, but if you get by the weird Template Haskell syntax, it is pretty straightforward.<br /><br />The second piece of code fromJson is a bit harder to understand. The first case is when there is a record. JSON.hs parses JS classes as a map from the string of the field to a JSON value. The obvious way to do this would be -<br /><br /> fromJson (J.Object m) = RecordName{ field1 = fromJson $ M.lookup m "field1";<br /> field2 = fromJson $ M.lookup m "field2"}<br /><br />However, we need to take into account that lookup or fromJson could fail. This means that we need to put it in a monad. I follow the lead of Data.Map and have fromJson return a value wrapped in a generic monad. To construct the record, we use the do syntax.<br /><br /> do<br /> f1<-M.lookup m "field1" >>=fromJson<br /> f2<-M.lookup m "field2" >>=fromJson<br /> return $ RecordName{field1=f1;field2=f2}<br /><br /> I'm not sure how to do this with template haskell, so we'll throw out the syntactic sugar for now.<br /><br /> M.lookup m "field1" >>=fromJson >>=<br /> (\f1-> M.lookup m "field2" >>=fromJson >>=<br /> (\f2 -> return $ RecordName{field1=f1;field2=f2}))<br /><br /> We need a couple of tricks to do this. First, we need unique names for the variables, f1 - fn. We do this by using the Q monad built into Template Haskell. One of the cool things that the Q monad lets us do is create unique variables. So the code -- let f = newName "f" ; do {f1<-f;f2<-f;f3<-f; return (f1,f2,f3)} -- will create three unique names.<br /><br /> The other issue we need to handle is to tie the variable names to the fields. We could just choose arbitary names, but let's find a cooler way to do this. If we were writing a compiler, what would we do? Use CPS! If we use CPS we can mimic the lambdas within code inside the Template Haskell. This way we can pass around an environment of bound variables. <br /> <br />>type Env = [(String,Name)]<br />>type Conti = Env -> ExpQ<br /><br />>fromJson :: Dec -> Q [Clause]<br />>fromJson dat = sequence $<br />> case dataCtors dat of<br />> [ctr] -><br />> [clause [return $ ConP ('JS.Object) [VarP m]] (normalB $ t sd) []] -- fromJson (JS.Object m) =<br />> where<br />> m = mkName "m" <br /><br /> {-M.lookup "str" m >>= DJ.fromJson >>= (\ f1. {cont})-}<br /><br />> t :: Conti -> ExpQ<br />> t = foldl (.) (\c->c []) $ <br />> map bindOne $ ctorFields ctr<br />> where<br />> bindOne str cont env = do<br />> var <- newName "f"<br />> [|M.lookup $(litQ str) $(varE m) >>= DJ.fromJson >>=<br />> $(lamE [varP var] $ (cont ((str,var):env))) |]<br /> <br /> {-Construct the record from the environment.<br /> return $ RecordName{field1=f1;field2=f2}-} <br /><br />> sd a = [| return $(rec a) |]<br />> where<br />> pair (s,n) = return (mkName s,VarE n)<br />> rec = recConE ( mkName $ ctorName ctr) . map pair<br /><br />The second case just reads in zero argument constructors as strings. Nothing exciting here.<br /><br />> ctrs -><br />> map (\s -> clause [return $ ConP ('JS.String) [LitP $ StringL s]]<br />> (normalB [|return $(conE $ mkName s)|]) []) $<br />> map ctorName ctrs<br /> <br />Anyway. I thought this was pretty cool. It is amazing what you can do with a little bit of template code. Gets you out of writing a ton of annoying processing code.Sashahttp://www.blogger.com/profile/07382747257624276619noreply@blogger.com0tag:blogger.com,1999:blog-8889927929894839318.post-67032622833877337262007-05-25T14:01:00.000-07:002007-05-25T14:05:55.778-07:00JGraph in HaskellOkay, so I was playing around with Jgraph to draw graphs for my thesis, and I really liked its simple interface and the clarity of the graphs it produced. I first used the program in Norman Ramsey's Programming languages class, so I have always associated it with programming languages. Anyway, I've been learning Haskell, so I thought what the heck, I'll try to use Parsec to write a stripped down version of JGraph in Haskell, and get some monad/literate programming practice. Here it is.<br /><br />>import Text.ParserCombinators.Parsec hiding (label)<br />>import Text.ParserCombinators.Parsec.Char<br />>import Text.ParserCombinators.Parsec.Token hiding (lexeme, symbol,natural,reserved)<br />>import qualified Text.ParserCombinators.Parsec.Token as P<br />>import Text.ParserCombinators.Parsec.Language (emptyDef)<br />>import Text.ParserCombinators.Parsec.Error<br />>import Graphics.HGL hiding (Point,char)<br />>import Graphics.HGL.Draw.Picture<br />>import Graphics.HGL.Draw.Text<br />>import Control.Monad<br />>import Data.Either<br /><br />-------------<br />The Type<br /><br />Our topmost type is a graph. It has a title, two axes, and many curves.<br /><br />>data Graph = Graph {title :: String,xaxis::Axis,yaxis::Axis,curves::[Curve]} deriving Show<br /><br />For now an axis is just labeled with a name. In the future, the axis will have a size.<br /><br />>data Axis = Axis {axisLab:: String} deriving Show<br /><br />A curve is a collection of points. Connected tells us if we should draw the connecting line.<br /><br />>data Curve = Curve { pts::[Point], connected::Bool} deriving Show<br />>type Point = (Int,Int)<br /><br />-------------<br />The Lexer<br /><br />Parsec does lexing and parsing simultaneously, but we still need to define the lexer. Our lexer just needs to throw away empty space and eat reserved words.<br /><br />>lexer :: TokenParser ()<br />>lexer = makeTokenParser<br />> (emptyDef<br />> { reservedNames = ["newgraph","newcurve","label","title","connected"]})<br /><br />These are just shortcuts to this lexer. You can think of lexer like an object. makeTokenParser is like new lexer() and P.natural lexer is like the method lexer.natural.<br /><br />>lexeme = P.lexeme lexer<br />>natural =P.natural lexer<br />>symbol = P.symbol lexer<br />>reserved = P.reserved lexer<br />>stringLit = P.stringLiteral lexer<br /><br />This one is a little more interesting. We eat a string and convert it to a boolean value.<br /><br />>boolLit = do<br />> t<-stringLit > case t of<br />> "true"->return True<br />> "false"->return False<br />> _ -> fail "Not boolean"<br /><br />------------<br />The Parser<br /><br />Our grammar in BNF<br /><br /><graph> ::= "newgraph" ("title" STRING)? <xaxis> <yaxis> <curve>*<br /><xaxis> ::= "xaxis" ("label" STRING)?<br /><yaxis> ::= "yaxis" ("label" STRING)?<br /><curve> ::= "newcurve" ("connected" BOOL)? <point>+<br /><point> ::= INT INT<br /><br />Looks just like our types! Cool. First we introduce two helper functions-<br />The first just eats a reserved word and continues.<br /><br />>label :: String -> Parser a -> Parser a<br />>label s p = do {reserved s; p}<br /><br />Second one tries a parse, and if it fails, returns a default value.<br /><br />>def :: a -> Parser a -> Parser a<br />>def d par = do {par <|> return d}<br /><br />We start our parsing with points. The Point parser just reads in two natural numbers and returns a point.<br /><br /><point> ::= INT INT<br /><br />>point :: Parser Point<br />>point = do<br />> a<-natural > b<-natural > return (fromInteger a, fromInteger b)<br /><br />The rest of the parsers are remarkably similar. They all first read in their label, and then their subparts. The function many1 is equivalent to our + notation above, it means read at least one of the items. Similarly The function many is equivalent to *. Finally the function def is our ? operator, it means try to read one, if it fails just return the default value.<br /><br /><curve> ::= "newcurve" ("connected" BOOL)? <point>+<br /><br />>curve :: Parser Curve<br />>curve = label "newcurve" $ do<br />> connected <- def False $ label "connected" boolLit > pts<-many1 point > return $ Curve {pts= pts,connected=connected}<br /><br /><yaxis> ::= "yaxis" ("label" STRING)?<br /><br />>axis :: String -> Parser Axis<br />>axis s = label s $ do<br />> lab <- def "" $ label "label" stringLit > return $ Axis lab<br /><br /><graph> ::= "newgraph" ("title" STRING)? <xaxis> <yaxis> <curve>*<br /><br />>graph :: Parser Graph<br />>graph = label "newgraph" $ do<br />> title <- def "" $ label "title" stringLit > xaxis <- axis "xaxis" > yaxis <- axis "yaxis" > curves <- many curve > return $ Graph{title = title,xaxis=xaxis,yaxis=yaxis,curves=curves}<br /><br />It is pretty neat how the BNF description and the Parser line up so nicely. This isn't always the case, but we had a prtty simple grammar to parse.<br /><br />--------------<br />The Graphics<br /><br />In JGraph we would draw out to a postscript file, but I'm not sure how to do that yet, so for now we just use HGL.<br />The program opens a window, draws the graph with the labels of the axes, a title, plots points, and possibly connects them .<br /><br />Start with some constants.<br /><br />>size = 300<br />>midH = size `div` 2<br />>midV = size `div` 2<br />>bot = size<br />>right = size<br /><br />This is just boilerplate, it creates a window and displays a graphic in the window.<br /><br />>createW :: Graphic -> IO()<br />>createW p = withWindow_ "HGraph" (right,bot) $ \w -> do<br />> drawInWindow w p<br />> getKey w<br /><br />Our Drawing functions will look just like the parsing functions. We traverse the Graph structure drawing each part.<br />First we draw a point.<br /><br />>drawPoint :: Point -> Graphic<br />>drawPoint (x,y)= ellipse (x-1,y-1) (x+1,y+1)<br /><br />Now we draw a curve. A curve is just a series of points. transPts moves the pts from the graph position to the screen position, and then the first line draws them on the screen.<br /><br />The second line connects the points together. We do this by zipping the points to the points shifted by one. i.e. if our points were [(1,2),(7,3),(4,6)] the code finds [(1,2),(7,3)] and [(7,3),(4,6)] and then runs line (1,2) (7,3), line (7,3) (4,6). I like this trick.<br /><br />>drawCurve :: Curve -> Graphic<br />>drawCurve cur = do<br />> overGraphics $ map drawPoint transPts<br />> if connected cur then overGraphics $ zipWith line (init transPts) (tail transPts)<br />> else emptyGraphic<br />> where transPts = map (\(x,y)->(midH + x,midV - y)) (pts cur)<br /><br />You might have noticed that we are taking<br />advantage of the fact that Graphic is a Monad (Yay!) which means we can draw in an imperative style.<br />For instance do {line (0,0) (1,1);line (0,1) (1,0)} will draw a cross. This is all that is going on an our last function . This just draws a bunch of things on the screen.<br /><br /><br />>drawGraph :: Graph -> Graphic<br />>drawGraph graph = do<br />> setTextAlignment (Center,Top)<br />> text (midH, 0) $ title graph<br />> axes<br />> overGraphics $ map drawCurve (curves graph)<br />> where axes = do line (midH,0) (midH,bot) <br />> text (midH,midV-midV `div` 2) $ axisLab $ yaxis graph<br />> line (0,midV) (right,midV)<br />> text (midH+midH `div` 2,midV) $ axisLab $ xaxis graph<br />> <br /><br />--------------<br />Main<br /><br />>main :: IO()<br />>main = do<br />> test <-readFile "test" > case parse graph "JGraph" test of<br />> Left v-> ioError $ userError $ show v<br />> Right g -> runGraphics $ createW $ drawGraph g<br /><br /><br />Here is a sample graph.<br /><br />newgraph title "My Graph" xaxis label "blah2" yaxis label "blah" newcurve connected "true" 0 0 4 28 100 100 title "hello"</curve></yaxis></xaxis></graph></yaxis></point></curve></point></point></point></curve></yaxis></xaxis></curve></yaxis></xaxis></graph>Sashahttp://www.blogger.com/profile/07382747257624276619noreply@blogger.com0