I am having trouble trying to format the output a of a list of my own type in Haskell.
I would like something like this:
Make | Model | Years(this is a list) <- this would be the headers if you like
-------------------
Item1 | Item1 | Item1s,Item1s
Item2 | Item2 | Item2s,Items2,Items2
^ This would be the data loaded from my String String [Int] type.
How would I do this in Haskell?
Generally, we use "pretty printing" libraries to do nice formatted output. The standard one that you should know is Text.PrettyPrint. Given a data type, you can walk that type, building up a well-formated document.
An example:
import Text.PrettyPrint
import Data.List
-- a type for records
data T = T { make :: String
, model :: String
, years :: [Int] }
deriving Show
-- test data
test =
[ T "foo" "avenger" [1990, 1992]
, T "bar" "eagle" [1980, 1982]
]
-- print lists of records: a header, then each row
draw :: [T] -> Doc
draw xs =
text "Make\t|\tModel\t|\tYear"
$+$
vcat (map row xs)
where
-- print a row
row t = foldl1 (<|>) [ text (make t)
, text (model t)
, foldl1 (<^>) (map int (years t))
]
-- helpers
x <|> y = x <> text "\t|\t" <> y
x <^> y = x <> text "," <+> y
Testing:
main = putStrLn (render (draw test))
Results in:
Make | Model | Year
foo | avenger | 1990, 1992
bar | eagle | 1980, 1982
The ability to quickly write pretty printers is an incredibly useful skill.
Here is a generalized table generator. It calculates the column widths to fit the widest row. The ColDesc
type allows you to specify, for each column, the title alignment, the title string, the data alignment, and a function to format the data.
import Data.List (transpose, intercalate)
-- a type for records
data T = T { make :: String
, model :: String
, years :: [Int] }
deriving Show
-- a type for fill functions
type Filler = Int -> String -> String
-- a type for describing table columns
data ColDesc t = ColDesc { colTitleFill :: Filler
, colTitle :: String
, colValueFill :: Filler
, colValue :: t -> String
}
-- test data
test =
[ T "foo" "avenger" [1990, 1992]
, T "bar" "eagle" [1980, 1982, 1983]
]
-- functions that fill a string (s) to a given width (n) by adding pad
-- character (c) to align left, right, or center
fillLeft c n s = s ++ replicate (n - length s) c
fillRight c n s = replicate (n - length s) c ++ s
fillCenter c n s = replicate l c ++ s ++ replicate r c
where x = n - length s
l = x `div` 2
r = x - l
-- functions that fill with spaces
left = fillLeft ' '
right = fillRight ' '
center = fillCenter ' '
-- converts a list of items into a table according to a list
-- of column descriptors
showTable :: [ColDesc t] -> [t] -> String
showTable cs ts =
let header = map colTitle cs
rows = [[colValue c t | c <- cs] | t <- ts]
widths = [maximum $ map length col | col <- transpose $ header : rows]
separator = intercalate "-+-" [replicate width '-' | width <- widths]
fillCols fill cols = intercalate " | " [fill c width col | (c, width, col) <- zip3 cs widths cols]
in
unlines $ fillCols colTitleFill header : separator : map (fillCols colValueFill) rows
Running:
putStrLn $ showTable [ ColDesc center "Make" left make
, ColDesc center "Model" left model
, ColDesc center "Year" right (intercalate ", " . map show . years)
] test
Results in:
Make | Model | Year
-----+---------+-----------------
foo | avenger | 1990, 1992
bar | eagle | 1980, 1982, 1983
Something like this?
import Data.List (intercalate)
data Foo = Foo String String [Int]
fooToLine :: Foo -> String
fooToLine (Foo a b cs) = a ++ " | " ++ b ++ " | " ++ intercalate ", " (map show cs)
Now, you can do
>>> fooToLine (Foo "Hello" "World" [1, 2, 3])
"Hello | World | 1, 2, 3"
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With