I got a data type "Cake"
*data Cake = EmptyBox | Food { name :: String
, flavors :: [Cake]} deriving( Read, Eq)*
instance Show Cake where
show cake = prints cake where
prints (Food name []) = name ++ "\n"
I want to print cakes like this (each cake got different flavors)
Cake1
Chocolate
Nutella
Strawberry
Cake2
Chocolate
Vanilla
Cake3
But I got error, doesn't works! How can I do it?
Since prints :: Cake -> String and flavors :: [Cake], we know
map prints flavors :: [String]
But this doesn't jive with the context in which it is used, since in
" " ++ expr
the " " is clearly a String and we therefore expect expr to be a String and not a [String].
To fix this, you should write or find a function which converts your [String] into a String in some way -- there are many candidate behaviors for this type, so you should decide what behavior you want and then find a way to achieve that behavior.
For test data, I used:
cake1 = Food{name="Cake1"
,flavors=[Food{name="Chocolate"
,flavors=[]}
,Food{name="Nutella"
,flavors=[Food{name="Strawberry"
,flavors=[]}]}]}
cake2 = Food{name="Cake2"
,flavors=[Food{name="Chocolate"
,flavors=[]}
,Food{name="Vanilla"
,flavors=[]}]}
cake3 = Food{name="Cake3"
,flavors=[]}
and wrote Show Cake as unlines composed with a function that has type Cake -> [String]
instance Show Cake where
show = unlines . prints where
prints :: Cake -> [String]
prints EmptyBox = []
prints (Food s []) = [s] -- a Food with no subflavors
prints (Food s fs) = s:concatMap (map (" "++) . prints) fs
That last line handles the general case of a Food with name s and flavors fs by mapping prints over fs, then mapping (" "++) over each sublist in that map, and concat'ing them together.
(" "++) :: String -> String
map (" "++) :: [String] -> [String]
map (" "++) . prints :: Cake -> [String]
map (map (" "++) . prints) :: [Cake] -> [[String]]
concat . map (map (" "++) . prints) :: [Cake] -> [String]
concatMap = concat . map
concatMap (map (" "++) . prints) :: [Cake] -> [String]
Each successive level of flavors adds an extra level of indentation (that's what the (" "++) is for). We can test it:
TestModule> putStrLn $ concatMap show [cake1, cake2, cake3]
Cake1
Chocolate
Nutella
Strawberry
Cake2
Chocolate
Vanilla
Cake3
Related
For my first line of Haskell I thought it'd be a nice case to produce a "natural listing" of items (of which the type supports show to get a string representation). By "natural listing" I mean summing up all items separated with , except the last one, which should read and lastitem. Ideally, I'd also like to not have a , before the "and".
To spice it up a bit (to show off the compactness of haskell), I wanted to have an "inline" solution, such that I can do
"My listing: " ++ ... mylist ... ++ ", that's our listing."
(Obviously for "production" making a function for that would be better in all ways, and allow for recursion naturally, but that's the whole point of my "inline" restriction for this exercise.)
For now I came up with:
main = do
-- hello
nicelist
nicelist = do
let is = [1..10]
putStrLn $ "My listing: " ++ concat [ a++b | (a,b) <- zip (map show is) (take (length is -1) $ repeat ", ") ++ [("and ", show $ last is)]] ++ ", that's our listing."
let cs = ["red", "green", "blue", "yellow"]
putStrLn $ "My listing: " ++ concat [ a++b | (a,b) <- zip (map show cs) (take (length cs -1) $ repeat ", ") ++ [("and ", show $ last cs)]] ++ ", that's our listing."
but this hardly seems optimal or elegant.
I'd love to hear your suggestions for a better solution.
EDIT:
Inspired by the comments and answer, I dropped the inline requirement and came up with the following, which seems pretty sleek. Would that be about as "haskellic" as we can get, or would there be improvements?
main = do
putStrLn $ "My listing: " ++ myListing [1..10] ++ ", that's the list!"
putStrLn $ "My listing: " ++ myListing ["red", "green", "blue", "yellow"] ++ ", that's the list!"
myListing :: (Show a) => [a] -> String
myListing [] = "<nothing to list>"
myListing [x] = "only " ++ (show x)
myListing [x, y] = (show x) ++ " and " ++ (show y)
myListing (h:t) = (show h) ++ ", " ++ myListing t
Here's how I would write it:
import Data.List
niceShow' :: [String] -> String
niceShow' [] = "<empty>"
niceShow' [a] = a
niceShow' [a, b] = a ++ " and " ++ b
niceShow' ls = intercalate ", " (init ls) ++ ", and " ++ last ls
niceShow :: [String] -> String
niceShow ls = "My listing: " ++ niceShow' ls ++ ", that's our listing."
niceList :: IO ()
nicelist = do
putStrLn $ niceShow $ show <$> [1..10]
putStrLn $ niceShow ["red", "green", "blue", "yellow"]
Steps:
Create niceShow to create your string
Replace list comprehensions with good old function calls
Know about intercalate and init
Add type signatures to top levels
Format nicely
niceShow can only be inlined if you know the size of the list beforehand, otherwise, you'd be skipping the edge cases.
Another way to state the rules for punctuating a list (without an Oxford comma) is this:
Append a comma after every element except the last two
Append “and” after the second-to-last element
Leave the final element unchanged
This can be implemented by zipping the list with a “pattern” list containing the functions to perform the modifications, which repeats on one end. We want something like:
repeat (<> ",") <> [(<> " and"), id]
But of course this is just an infinite list of the comma function, so it will never get past the commas and on to the “and”. One solution is to reverse both the pattern list and the input list, and use zipWith ($) to combine them. But we can avoid the repeated reversals by using foldr to zip “in reverse” (actually, just right-associatively) from the tail end of the input. Then the result is simple:
punctuate :: [String] -> [String]
punctuate = zipBack
$ [id, (<> " and")] <> repeat (<> ",")
zipBack :: [a -> b] -> [a] -> [b]
zipBack fs0 = fst . foldr
(\ x (acc, f : fs) -> (f x : acc, fs))
([], fs0)
Example uses:
> test = putStrLn . unwords . punctuate . words
> test "this"
this
> test "this that"
this and that
> test "this that these"
this, that and these
> test "this that these those them"
this, that, these, those and them
There are several good ways to generalise this:
zipBack is partial—it assumes the function list is infinite, or at least as long as the string list; consider different ways you could make it total, e.g. by modifying fs0 or the lambda
The punctuation and conjunction can be made into parameters, so you could use e.g. semicolons and “or”
zipBack could work for more general types of lists, Foldable containers, and functions (i.e. zipBackWith)
String could be replaced with an arbitrary Semigroup or Monoid
There’s also a cute specialisation possible—if you want to add the option to include an Oxford comma, its presence in the “pattern” (function list) depends on the length of the final list, because it should not be included for lists of 2 elements. Now, if only we could refer to the eventual result of a computation while computing it…
I tried break line using \n, putStrLn and print but nothing works.
When I use \n the result only concatenates the strings, and when I use putStrLn or print I receive a type error.
Output for \n:
formatLines [("a",12),("b",13),("c",14)]
"a...............12\nb...............13\nc...............14\n"
Output for putStrLn:
format.hs:6:22:
Couldn't match type `IO ()' with `[Char]'
Expected type: String
Actual type: IO ()
In the return type of a call of `putStrLn'
In the expression:
putStrLn (formatLine ((fst x), (snd x)) ++ formatLines xs)
In an equation for `formatLines':
formatLines (x : xs)
= putStrLn (formatLine ((fst x), (snd x)) ++ formatLines xs)
Failed, modules loaded: none.
the output for print is the same as that of putStrLn
Here is my code:
formatLine :: (String,Integer) -> String
formatLine (s, i) = s ++ "..............." ++ show i
formatLines::[(String,Integer)] -> String
formatLines [] = ""
formatLines (x:xs) = print (formatLine ((fst x), (snd x)) ++ formatLines xs)
I understand the reason of the error for print and putStrLn but i have no idea how fix it.
Split your code in two parts.
One part simply constructs the string. Use "\n" for newlines.
The second part takes the string and applies putStrLn (NOT print) to it. The newlines will get printed correctly.
Example:
foo :: String -> Int -> String
foo s n = s ++ "\n" ++ show (n*10) ++ "\n" ++ s
bar :: IO ()
bar = putStrLn (foo "abc" 42)
-- or putStr (...) for no trailing newline
baz :: String -> IO ()
baz s = putStrLn (foo s 21)
If you use print instead, you'll print the string representation, with quotes and escapes (like \n) inside it. Use print only for values that have to be converted to string, like numbers.
Also note that you can only do IO (like printing stuff) in functions whose return type is IO (something).
You need to print the results to output.
This is an IO action, and so you cannot have a function signature ending with -> String. Instead, as #chi points out, the return type should be IO (). Further, since you have the function to generate formatted string already, all you need is a function to help you map the printing action over your input list. This you can do using mapM_, like so:
formatLines::[(String,Integer)] -> IO ()
formatLines y = mapM_ (putStrLn . formatLine) y
Demo
Imagine I read an input block via stdin that looks like this:
3
12
16
19
The first number is the number of following rows. I have to process these numbers via a function and report the results separated by a space.
So I wrote this main function:
main = do
num <- readLn
putStrLn $ intercalate " " [ show $ myFunc $ read getLine | c <- [1..num]]
Of course that function doesn't compile because of the read getLine.
But what is the correct (read: the Haskell way) way to do this properly? Is it even possible to write this function as a one-liner?
Is it even possible to write this function as a one-liner?
Well, it is, and it's kind of concise, but see for yourself:
main = interact $ unwords . map (show . myFunc . read) . drop 1 . lines
So, how does this work?
interact :: (String -> String) -> IO () takes all contents from STDIN, passes it through the given function, and prints the output.
We use unwords . map (show . myFunc . read) . drop 1 . lines :: String -> String:
lines :: String -> [String] breaks a string at line ends.
drop 1 removes the first line, as we don't actually need the number of lines.
map (show . myFunc . read) converts each String to the correct type, uses myFunc, and then converts it back to a `String.
unwords is basically the same as intercalate " ".
However, keep in mind that interact isn't very GHCi friendly.
You can build a list of monadic actions with <$> (or fmap) and execute them all with sequence.
λ intercalate " " <$> sequence [show . (2*) . read <$> getLine | _ <- [1..4]]
1
2
3
4
"2 4 6 8"
Is it even possible to write this function as a one-liner?
Sure, but there is a problem with the last line of your main function. Because you're trying to apply intercalate " " to
[ show $ myFunc $ read getLine | c <- [1..num]]
I'm guessing you expect the latter to have type [String], but it is in fact not a well-typed expression. How can that be fixed? Let's first define
getOneInt :: IO Int
getOneInt = read <$> getLine
for convenience (we'll be using it multiple times in our code). Now, what you meant is probably something like
[ show . myFunc <$> getOneInt | c <- [1..num]]
which, if the type of myFunc aligns with the rest, has type [IO String]. You can then pass that to sequence in order to get a value of type IO [String] instead. Finally, you can "pass" that (using =<<) to
putStrLn . intercalate " "
in order to get the desired one-liner:
import Control.Monad ( replicateM )
import Data.List ( intercalate )
main :: IO ()
main = do
num <- getOneInt
putStrLn . intercalate " " =<< sequence [ show . myFunc <$> getOneInt | c <- [1..num]]
where
myFunc = (* 3) -- for example
getOneInt :: IO Int
getOneInt = read <$> getLine
In GHCi:
λ> main
3
45
23
1
135 69 3
Is the code idiomatic and readable, though? Not so much, in my opinion...
[...] what is the correct (read: the Haskell way) way to do this properly?
There is no "correct" way of doing it, but the following just feels more natural and readable to me:
import Control.Monad ( replicateM )
import Data.List ( intercalate )
main :: IO ()
main = do
n <- getOneInt
ns <- replicateM n getOneInt
putStrLn $ intercalate " " $ map (show . myFunc) ns
where
myFunc = (* 3) -- replace by your own function
getOneInt :: IO Int
getOneInt = read <$> getLine
Alternatively, if you want to eschew the do notation:
main =
getOneInt >>=
flip replicateM getOneInt >>=
putStrLn . intercalate " " . map (show . myFunc)
where
myFunc = (* 3) -- replace by your own function
I have a list of tuples. For example: [("A",100,1),("B",101,2)]. I need to display it in a simple way. For example: "your name is: A", "Your id is: 100".
If anyone can find a solution for this, it would be a great help. Thanks in advance.
The easiest way to do this is to create a function that works for one of the elements in your list. So you'll need something like:
showDetails :: (String, Int, Int) -> String
showDetails (name, uid, _) = "Your name is:" ++ name ++ " Your ID is: " ++ show uid
Then you would apply this function to each element in the list, which means you want to use the mapping function:
map :: (a -> b) -> [a] -> [b]
So, if your list is called xs, you would want something like:
map showDetails xs
This obviously gives you a result of type [String], so you might be interested in the unlines function:
unlines :: [String] -> String
This simply takes a list of strings, and creates a string where each element is separated by a new line.
Putting this all together, then, gives you:
main :: IO ()
main = putStrLn . unlines . map showDetails $ [("A",100,1),("B",101,2)]
For a single tuple, just pattern match all the elements, and do something with them. Having a function that does that, you can use map to transform the entire list.
import Data.List (foldl')
show_tuple :: (Num a, Num b) => (String, a, b) -> String
show_tuple (name, id, something) =
"Your name is: " ++ name ++ "\n" ++
"Your ID is: " ++ (show id) ++ "\n" ++
"Your something: " ++ (show something) ++ "\n\n"
-- transforms the list, and then concatenates it into a single string
show_tuple_list :: (Num a, Num b) => [(String, a, b)] -> String
show_tuple_list = (foldl' (++) "") . (map show_tuple)
The output:
*Main Data.List> putStr $ show_tuple_list [("ab", 2, 3), ("cd", 4, 5)]
Your name is: ab
Your ID is: 2
Your something: 3
Your name is: cd
Your ID is: 4
Your something: 5
Quick and dirty solution
f (x,y,z) = "your id is " ++ (show y) ++ ", your name is " ++ (show x) ++ "\n"
main = putStrLn $ foldr (++) "" (map f [("A",100,1),("B",101,2)])
OR (by #maksenov)
main = putStrLn $ concatMap f [("A",100,1),("B",101,2)]
Please try:
get1st (a,_,_) = a
get2nd (_,a,_) = a
get3rd (_,_,a) = a
showTuples [] = ""
showTuples (x:xs) = "Your name is:" ++ show(get1st(x)) ++ " Your ID is: " ++ show(get2nd(x)) ++ "\n" ++ showTuples xs
main = do
let x = [("A",100,1),("B",101,2)]
putStrLn . showTuples $ x
I have the following functions in Haskell that must print the sales of weeks. Each sale in a new line. But it is not working the way i expect it to. The problem i have is the newline character '\n'.
Code:
printWeeks :: Int->String
printWeeks 0 = printWeek 0
printWeeks x = printWeeks(x-1) ++ printWeek x
printWeek :: Int->String
printWeek x = show(x) ++ " " ++ stars (sales x) ++ "'\n'"
I have tried many ways but the new line character is not working as expected. Everything is printed on the same line whichis not what i want.
Need help?
thanks
UPDATE
The following is not working because of compile errors. The errors comes from the second line of formatLines. The type decalaration is causing errors. Need help here
formatLine :: (Name,Price)->IO()
formatLine (a,b) = putStrLn (a ++ dots ++ p)
where
x=(length a)
p=(formatPence b)
y=length p
z=lineLength-(x+y)
dots = printDots z
formatLines :: [(Name,Price)]->IO()
formatLines []= ""
formatLines (a:x) = formatLines x ++ formatLine a
You should use ++ "\n" to append a newline to the output; your current code will add a ', then a newline, then another '.
As #marcog points out, be sure to use putStr to print it out (or don't append the newline at all and use putStrLn). Example:
Hugs> putStr (show 4 ++ "\n")
4
Hugs> putStrLn (show 4 ++ "\n")
4
Hugs> print (show 4 ++ "\n")
"4\n"
(Note that the Hugs interpreter adds extra newlines after each output.)
You are probably printing the string using print x, which is equivalent to putStrLn (show x). show x is converting the newlines into readable characters \ and n. You need to use putStrLn x instead, or putStr x if you don't want to append a newline to the end of the string.
You should also remove the single quotes you have around the newline, unless that was intentional.
It's a bit of a riddle why so much action is happening under the heading of IO. This is maybe a little verbose. I couldn't tell where lineLength was coming from so I made it a parameter.
formatLine :: Int -> (Name,Price) -> String
formatLine linelength (name, price) = name ++ dotfill ++ showprice
where
showprice :: String
showprice = formatPence price
extra :: Int
extra = linelength - length (name ++ showprice)
dotfill :: String
dotfill = replicate extra '.'
formatLines :: Int -> [(Name, Price)] -> String
formatLines linelength []= ""
formatLines linelength (first:rest) =
(formatLine linelength first ++ "\n") ++ formatLines linelength rest
standardPrint :: [(Name, Price)] -> IO ()
standardPrint listing = putStrLn (formatLines 50 listing)
fileAwayPrices :: FilePath -> [(Name,Price)] -> IO()
fileAwayPrices filename listing = writeFile filename (formatLines 70 listing)
testlist :: [(Name,Price)]
testlist = [("oats",344),("barley", 299),("quinoa",599)]
-- *Main> standardPrint testlist
-- oats...........................................344
-- barley.........................................299
-- quinoa.........................................599
type Name = String
type Price = Integer
formatPence n = show n
Re your update: your type declaration is correct, it's the rest of formatLines that's wrong.
formatLines :: [(Name,Price)]->IO()
formatLines [] = return ()
formatLines (a:x) = formatLines x >> formatLine a
A more concise way of writing that is
formatLines :: [(Name,Price)]->IO()
formatLines = mapM_ formatLine . reverse