[Haskell-cafe] Translating perl -> haskell, string "fill ins" with an error on invalid input seems awfully complex. Is there a way to simplify?

101 views
Skip to first unread message

Thomas Hartman

unread,
Apr 12, 2007, 6:56:49 AM4/12/07
to Haskell-cafe
I was translating some perl code to haskell as a learning exercise and
wound up with the following. (below) Simple code that accepts some
string arguments, and prints a string -- so, of type String -> String
-> String -> String -> IO ().

I like to be concise, but I get the feeling something went awry. What
seems to be costing me the most is checking whether the various
arguments are legitimate, and printing a helpful error message if not.

I was able to achieve this by using Maybe and error on failed pattern
match, but as said, seems kind of overly complicated.

Is there a simpler way to do the following, eg for function

gen_gnuplot_financial_script :: String -> String -> String -> String -> IO ()

?

By the way this is being used in

http://code.google.com/p/gnuplotwebinterface/

**************

module Common where

gnuplot_png_settings = "set terminal png transparent nocrop enhanced
size 600,400\n" ++
"set pm3d implicit at s"

gnuplot_math_settings = gnuplot_png_settings ++ "\n" ++
"set border 4095 \n\
\ set xlabel \"x\" \n\
\ set ylabel \"y\""

gnuplot_timeseries_settings = gnuplot_png_settings ++ "\n" ++
"set xdata time # The x axis
data is time \n" ++
"set timefmt \"%d-%b-%y\" # The dates in
the file look like 10-Jun-04 \n" ++
"set format x \"%b %d\" #On the
x-axis, we want tics like Jun 10"


gen_gnuplot_math_script :: String -> String -> IO ()
gen_gnuplot_math_script style function = let maybePlotCmd = lookup
style style_to_plotcmd
style_to_plotcmd =
[("math-2d","plot"),("math-3d","splot")]
in case maybePlotCmd of
Just plotcmd ->
putStrLn $ gnuplot_math_settings ++ "\n" ++ plotcmd ++ " " ++
function
_ -> error
$ "bad style: " ++ style

gen_gnuplot_financial_script :: String -> String -> String -> String -> IO ()
gen_gnuplot_financial_script company displaymode startDate endDate
= let maybeCompanyFile = lookup company company_to_companyfile
maybeModeString = lookup displaymode displaymode_to_modestring
maybeTitleEnd = lookup displaymode displaymode_to_titleend
company_to_companyfile =
[("ibm","data/ibm.dat"),("cisco","data/cisco.dat")]
displaymode_to_modestring = [("points", "using 1:2 with linespoints"),
("candles","using
1:($2+$3+$4+$5)/4:4:3 with yerrorbars")]
displaymode_to_titleend = [("points","daily
prices"),("candles","opening prices")]
in case ( maybeCompanyFile,
maybeModeString,
maybeTitleEnd ) of
( Just companyfile,
Just modestring,
Just titleEnd) -> putStrLn $
gnuplot_timeseries_settings ++ "\n" ++
"plot [\"" ++ startDate ++ "\":\"" ++
endDate ++ "\"]"
++ " '" ++ companyfile ++ "'"
++ modestring
++ " title \"" ++ company ++ " " ++
titleEnd ++ "\""
_ -> error $ "bad lookup. " ++ company ++ " ->
company file: " ++ ( show maybeCompanyFile ) ++ "\n" ++
" " ++ displaymode ++ " ->
displaymode: " ++ ( show maybeModeString ) ++ "\n" ++
" " ++ displaymode ++ " ->
titleEnd: " ++ ( show maybeTitleEnd)
_______________________________________________
Haskell-Cafe mailing list
Haskel...@haskell.org
http://www.haskell.org/mailman/listinfo/haskell-cafe

Dan Mead

unread,
Apr 12, 2007, 10:57:58 AM4/12/07
to Thomas Hartman
I believe there is a library which lets you do do perl style REGEX matching

maybe you should check that out

Jason Orendorff

unread,
Apr 12, 2007, 1:09:13 PM4/12/07
to
On Apr 12, 6:56 am, "Thomas Hartman" <tphya...@gmail.com> wrote:
> What seems to be costing me the most is checking whether the various
> arguments are legitimate, and printing a helpful error message if not.
>
> I was able to achieve this by using Maybe and error on failed pattern
> match, but as said, seems kind of overly complicated.

It's actually *more* code for this small example, but for serious
programs I think you would use exceptions.

How your program might look with exceptions
http://hpaste.org/1359#a1

-j

Thomas Hartman

unread,
Apr 13, 2007, 9:04:28 AM4/13/07
to Haskell-cafe
Answering my own plea for help, I now have the following, which seems
neater to me.

company_to_companyfile = [("ibm","data/ibm.dat"),("cisco","data/cisco.dat")]
displaymode_to_modestring = [("points", "using 1:2 with linespoints"),
("candles","using 1:($2+$3+$4+$5)/4:4:3 with yerrorbars")]
displaymode_to_titleend = [("points","daily
prices"),("candles","opening prices")]

financial_output_wrapper :: String -> String -> String -> String -> IO ()
financial_output_wrapper company displaymode startDate endDate =
do


let maybeCompanyFile = lookup company company_to_companyfile

case maybeCompanyFile of Nothing -> error $ "no company file for "
++ company
_ -> return ()
let maybeModeString = lookup displaymode displaymode_to_modestring
case maybeModeString of Nothing -> error $ "no mode string for "
++ displaymode
_ -> return ()
let maybeTitleEnd = lookup displaymode displaymode_to_titleend
case maybeTitleEnd of Nothing -> error $ "no title end for " ++ displaymode
_ -> return ()
let maybeScript = gen_gnuplot_financial_script company
( maybeCompanyFile )
( maybeModeString )
( maybeTitleEnd )
startDate endDate
case maybeScript of
Just script -> putStrLn script
_ -> error $ "bad script"

gen_gnuplot_financial_script :: String -> Maybe String -> Maybe String
-> Maybe String -> String -> String -> Maybe String
gen_gnuplot_financial_script company (Just companyfile ) ( Just
modestring) ( Just titleEnd ) startDate endDate
= Just $ gnuplot_timeseries_settings ++ "\n" ++


"plot [\"" ++ startDate ++ "\":\"" ++
endDate ++ "\"]"
++ " '" ++ companyfile ++ "'"
++ modestring
++ " title \"" ++ company ++ " " ++
titleEnd ++ "\""

2007/4/12, Thomas Hartman <tphy...@gmail.com>:

Thomas Hartman

unread,
Apr 13, 2007, 9:22:03 AM4/13/07
to Haskell-cafe
And this mess can be simplified further by something like

financial_output_wrapper company displaymode startDate endDate =
do
let maybeCompanyFile = lookup company company_to_companyfile

validate_arg "company" company maybeCompanyFile

.......

validate_arg argname arg maybeTransformedArg =
case maybeTransformedArg of
Nothing -> error $ "no transformed " ++ argname ++ " arg for " ++ arg
_ -> return ()

Summary: I like being able to check the validity of user input on an
arg-by-arg basis, and now I guess I can.


2007/4/13, Thomas Hartman <tphy...@gmail.com>:

Claus Reinke

unread,
Apr 13, 2007, 9:45:59 AM4/13/07
to Thomas Hartman, Haskell-cafe
> Answering my own plea for help, I now have the following, which seems
> neater to me.

checking Maybes is best done in the Maybe Monad, or if you need specific error
messages, using maybe. that, in turn can be abstracted out into a lookup with error
message. once the checking is done in the wrapper, there is no need to repeat it in
the generator. also, the interface to the generator is too wide for the small amount of
extra functionality it provides, so it is probably best inlined, and there seems to be no
need to commit to IO so early. i also tend to use [String], with a final unlines before
output, but that is a matter of opinion, i guess.

financial_output :: String -> String -> String -> String -> String
financial_output company displaymode startDate endDate = financial_script
where
financial_script = gnuplot_timeseries_settings ++ "\n"


++ "plot [\"" ++ startDate ++ "\":\"" ++ endDate ++ "\"]"

++ " '" ++ companyFile ++ "'" ++ modeString


++ " title \"" ++ company ++ " " ++ titleEnd ++ "\""

companyFile = lookupWith (error $ "no company file for " ++ company)
company company_to_companyfile

modeString = lookupWith (error $ "no mode string for " ++ displaymode)
displaymode displaymode_to_modestring

titleEnd = lookupWith (error $ "no title end for " ++ displaymode)
displaymode displaymode_to_titleend

lookupWith error key assocs = maybe error id $ lookup key assocs

hth,
claus

Evan Laforge

unread,
Apr 13, 2007, 1:42:59 PM4/13/07
to Claus Reinke
> financial_script = gnuplot_timeseries_settings ++ "\n"
> ++ "plot [\"" ++ startDate ++ "\":\"" ++ endDate ++ "\"]"
> ++ " '" ++ companyFile ++ "'" ++ modeString
> ++ " title \"" ++ company ++ " " ++ titleEnd ++ "\""

Also note the existence of Text.Printf if you like that style better.

Claus Reinke

unread,
Apr 14, 2007, 8:42:14 AM4/14/07
to Thomas Hartman
by utilizing Text.Printf.printf, extracting some more common functionality for the lookups,
and changing the error handling (check for errors before giving results, but use throwError
instead of error, letting the caller decide whether errors are fatal or not), we arrive at
something like:

financial_output :: (Functor m, MonadError String m)
=> String -> String -> String -> String -> m String


financial_output company displaymode startDate endDate =

fmap financial_script $ mapM lookupWith lookups
where
financial_script [companyFile,modeString,titleEnd] =
gnuplot_timeseries_settings ++ "\n"
++ printf "plot [\"%s\":\"%s\"] '%s'%s title \"%s %s\""
startDate endDate companyFile modeString company titleEnd

lookups = [ ("no company file for ", company, company_to_companyfile)
, ("no mode string for ", displaymode, displaymode_to_modestring)
, ("no title end for ", displaymode, displaymode_to_titleend)
]

lookupWith (msg,key,assocs) = maybe (throwError $ msg ++ key) return $ lookup key assocs

which perhaps isn't all that bad? the main thing i miss in Haskell for this kind of code
generators are here-documents. there are workarounds (Hugs has a form of here docs,
string interpolation isn't difficult to hack up, unlines gets rid of ++ and "\n"), and for
more complex code generators, use of Text.PrettyPrint may be more appropriate, but
for everyday scripting with code generation, nothing is as simple, readable, or portable
as good old here-documents.

hth,
claus

ps. calling the modified function:

Main> either error putStrLn $ financial_output "ibm" "point" "start" "end"
Program error: no mode string for point

Main> either error putStrLn $ financial_output "ibm" "points" "start" "end"

set terminal png transparent nocrop enhanced size 600,400

set pm3d implicit at s

set xdata time # The x axis data is time

set timefmt "%d-%b-%y" # The dates in the file look like 10-Jun-04

set format x "%b %d" #On the x-axis, we want tics like Jun 10

plot ["start":"end"] 'data/ibm.dat'using 1:2 with linespoints title "ibm daily prices"

Thomas Hartman

unread,
Apr 15, 2007, 7:04:13 AM4/15/07
to Haskell-cafe
Claus and Evan ++; that was very helpful.

FWIW, my gut feeling is that Claus's first version was easier to
understand than the revision with printf, which seems to me to involve
a lot more monadic wizardry (Functor, MonadError, fmap, mapm). The
first version, which just used maybe, was clear to me within seconds.

But again, I learned a lot. Thanks.

jeff p

unread,
Apr 15, 2007, 11:20:30 PM4/15/07
to Thomas Hartman
{----

Hello,

Here is a variation on Claus' code which returns an Either type
rather than fails with error. This could be further generalized to use
any instance of MonadError, rather than Either.

-Jeff

----}

import Control.Monad.Error

financial_output :: String -> String -> String -> String -> Either String String
financial_output company displaymode startDate endDate = financial_script
where
financial_script = gnuplot_timeseries_settings <++> "\n"
<++> "plot [\"" <++> startDate <++> "\":\""


<++> endDate <++> "\"]"
<++> " '" <++> companyFile <++> "'" <++> modeString
<++> " title \"" <++> company <++> " " <++>
titleEnd <++> "\""

companyFile = lookupWith ("no company file for " ++ company)
company company_to_companyfile

modeString = lookupWith ("no mode string for " ++ displaymode)
displaymode displaymode_to_modestring

titleEnd = lookupWith ("no title end for " ++ displaymode)
displaymode displaymode_to_titleend

lookupWith :: (Eq a) => String -> a -> [(a,String)] -> Either String String
lookupWith error key assocs = maybe (Left error) Right $ lookup key assocs

class MyString a
where mystr :: a -> Either String String
instance MyString (Either String String)
where mystr = id
instance MyString String
where mystr = Right

x <++> y = do xv <- mystr x
yv <- mystr y
return $ xv ++ yv

Thomas Hartman

unread,
Apr 16, 2007, 7:45:24 AM4/16/07
to Haskell-cafe
With regards to the variable interpolation in strings problem, it's
probably worth watching

http://groups.google.de/group/fa.haskell/browse_thread/thread/34741c2a5c311a17/286dbd62748ef1c1?lnk=st&q=%22haskell+cafe%22+%22template+system%22&rnum=1&hl=en#286dbd62748ef1c1

which mentions some perl/python-like template systems in the works for haskell.

2007/4/16, jeff p <mut...@gmail.com>:

Claus Reinke

unread,
Apr 16, 2007, 9:18:34 AM4/16/07
to Thomas Hartman, Haskell-cafe
> With regards to the variable interpolation in strings problem, ..

as i mentioned, it is not difficult to hack something up, and in many cases,
efficiency doesn't matter much for this part of the problem (though a standard,
efficient, well-designed library would be welcome). but if we compare the attached
example with here docs in perl or shell or .., we find that we can get rid of all that
escaping and concatenation in strings, but:

- the file name should be implicit (the current source)
- the dictionary should be implicit (the current variable environment)

the former might be easy to add to the language, and the latter would be a minor
subset of template haskell functionality. but they need to be standardized and
widely available to be of much use.

claus

ps here's the output:

$ runhaskell Here.hs

this is a poor man's here-document

with quotes ", and escapes \,
and line-breaks, and layout
without escaping \" \\ \n,
without concatenation.

oh, and with some variables, $(too).

<html>
<head><title>very important page</title></head>
<body>
<verb>

this is a poor man's here-document

with quotes ", and escapes \,
and line-breaks, and layout
without escaping \" \\ \n,
without concatenation.

oh, and with some variables, $(too).


</verb>
</body>
</html>

Here.hs

Thomas Hartman

unread,
Apr 16, 2007, 9:58:34 AM4/16/07
to Haskell-cafe
I put this on the haskell wiki at

http://haskell.org/haskellwiki/Poor_Man%27s_Heredoc_in_Haskell

So far I have only linked this from
http://haskell.org/haskellwiki/Simple_unix_tools

I feel like the wiki deserves a section on "Haskell Template
Solutions" distinct from this. However, there is a bit of a namespace
conflict, as "template haskell" is a ghc extension, and so if you
google on it you will get all stuff first.

I wonder if anyone has an opinion on a good title for a wiki page about this.

Haskell String Interpolation?
Haskell String Templates?

But these sound clunky to me.

2007/4/16, Claus Reinke <claus....@talk21.com>:

Thomas Hartman

unread,
Apr 18, 2007, 8:34:25 AM4/18/07
to jeff p
Looks like this needs to be run with

#!/usr/lib/ghc-6.6/bin/runghc
{-# OPTIONS_GHC -fglasgow-exts #-}

to get

instance MyString (Either String String)
where mystr = id

instance MyString String
where mystr = Right

to work.

I'm curious if there is a community feeling on whether glasgow-exts is
sort of a de-facto standard now? Or is it common to try to get things
to work without this, for maximum portability?

Is there an easy way to get the above to work without the compiler flag?


2007/4/16, jeff p <mut...@gmail.com>:

Thomas Hartman

unread,
Apr 18, 2007, 8:36:54 AM4/18/07
to jeff p
Without the flag:

thartman@linodehaskell:~/websites/gnuplotwebinterface/cgi-bin/gnuplot-scripts>./financial2.hs
cisco candles 31-May-04 11-Jun-04

/financial2.hs:58:0:
Illegal instance declaration for `MyString (Either String String)'
(The instance type must be of form (T a b c)
where T is not a synonym, and a,b,c are distinct type variables)
In the instance declaration for `MyString (Either String String)'

/financial2.hs:61:0:
Illegal instance declaration for `MyString String'
(The instance type must be of form (T a b c)
where T is not a synonym, and a,b,c are distinct type variables)
In the instance declaration for `MyString String'

thartman@linodehaskell:~/websites/gnuplotwebinterface/cgi-bin/gnuplot-scripts>cat
--number financial2.hs | tail
58 instance MyString (Either String String)
59 where mystr = id
60
61 instance MyString String
62 where mystr = Right
63
64 x <++> y = do xv <- mystr x
65 yv <- mystr y
66 return $ xv ++ yv
67


2007/4/18, Thomas Hartman <tphy...@gmail.com>:

Reply all
Reply to author
Forward
0 new messages