Reputation: 5709
I want to show the content of lists with arbitrary types, one element per line, numbered starting at 1 like this :
String Example:
> bs "Hallo"
1. 'H'
2. 'a'
3. 'l'
4. 'l'
5. 'o'
Integer Example
> bs [5,6,1,2]
1. 5
2. 6
3. 1
4. 2
Tuples Example
> bs [(4,"Test"),(3,"Aye"),(5,"Fives")]
1. (4,"Test")
2. (3,"Ayes")
3. (4,"Fives)
I found this to be one solution:
bs' :: Show a => [a] -> Integer -> IO ()
bs' [] _ = return ()
bs' (x:xs) y = do
putStrLn $ (show y) ++ ". " ++ (show x)
bs' xs $ succ y
bs x = bs' x 1
As I am absolute beginner to Haskell I wonder what is the "best" way to solve this problem? Am I on the right trail or is that just plain "bad" Haskell.
How to output the Chars in the String example without the '' and still be able to output any type which has an instance of Show ?
I would like to know about other ways to solve this task, from different perspectives like: readability, efficiency, code reuse.
I also did it like this and found it even stranger (but somehow cool):
bs' :: Show a => [(Integer,a)] -> IO ()
bs' [] = return ()
bs' ((x1,x2):xs) = do
putStrLn $ (show x1) ++ ". " ++ (show x2)
bs' xs
bs x = bs' (zip [1..] x)
I have done about 25 years of imperative programming and being really interested in learning something new. At the same time if feels incredible "strange" to code in Haskell and I still can't imagine how a big project is done with this "crazy language from the moon" :)
EDIT: I wanna thank everybody. I choose one Answer because I have to but all are very helpful! I also want to say that the top solution I had was because "in the real problem" where that came from I had to skip some list elements and the numbering got wrong when using the zip approach. After reading all the answers I am pretty sure, that even then the solution is to first filter the list and then zip map the output function.
Upvotes: 5
Views: 1549
Reputation: 64740
It is good to decompose your task into small parts. In this case you want to 1) render each line by showing the element and prepending a number then 2. Print out each rendering on its own line in a terminal.
So the rendering is just some string munging:
renderLine :: Show a => Integer -> a -> String
renderLine i a = show i ++ ". " ++ show a
And the combination of many lines needs to pass in successive numbers into the render:
bs :: Show a => [a] -> String
bs = unlines . zipWith renderLine [1..]
This gives us results such as:
*Main> putStr $ bs "Hello"
1. 'H'
2. 'e'
3. 'l'
4. 'l'
5. 'o'
*Main> putStr $ bs [1,2,3,4]
1. 1
2. 2
3. 3
4. 4
*Main> putStr $ bs [(4,"Test"),(3,"Aye"),(5,"Fives")]
1. (4,"Test")
2. (3,"Aye")
3. (5,"Fives")
Questions
As I am absolute beginner to Haskell I wonder what is the "best" way to solve this problem? Am I on the right trail or is that just plain "bad" Haskell.
I'd say the best way is the one that is cleanest to read for practiced Haskell programmers, which usually means using the common functions from Prelude, such as zipWith
, and avoiding manual primitive recursion when possible.
How to output the Chars in the String example without the '' and still be able to output any type which has an instance of Show ?
To perform different operations on different types with the same function you'll need a type class.
EDIT I didn't read carefully enough. I now see you wanted to make this work for anything that is an instance of Show
.
There are many long answers that can (and probably will) be given here about how making Char
behave one way but then effectively lift all other Show
instances leaves some ambiguity which the compiler must resolve. I'll skip that and just tell you we need several extensions to the language via the {-# LANGUAGE ... #-}
pragma you see below:
{-# LANGUAGE UndecidableInstances #-}
{-# LANGUAGE OverlappingInstances #-}
{-# LANGUAGE FlexibleInstances #-}
{-# LANGUAGE IncoherentInstances #-}
renderLine :: BS a => Integer -> a -> String
renderLine i a = show i ++ ". " ++ r a
bs :: BS a => [a] -> String
bs = unlines . zipWith renderLine [1..]
class BS a where
r :: a -> String
instance BS Char where
r c = [c]
instance (Show a) => BS a where
r = show
And in practice:
*Main> putStr $ bs [(4,"Test"),(3,"Aye"),(5,"Fives")]
1. (4,"Test")
2. (3,"Aye")
3. (5,"Fives")
*Main> putStr $ bs "Hello"
1. H
2. e
3. l
4. l
5. o
Upvotes: 4
Reputation: 22596
To answer question 1: zip [1..]
or variants is the way to do it. It exactly means what you want.
About question 2: the problem of Show
is it's used for two purpose, printing things and serializing thing. In theory, show
should be the opposite of Read
and so should include all information needed to reconstruct the data from a string. So show
adds quote and double quote to char and string and there is nothing you can do about it.
What you can do is,
display
function which strips quote and double quotes (shouldn't be too hard
to do and it's a good exercise if you are learning haskell)printf
with %v
which use The "%v" specifier is provided for all built-in types, and should be provided for user-defined type formatters as well. It picks a "best" representation for the given type. For the built-in types the "%v" specifier is converted as follows:
c Char
u other unsigned Integral
d other signed Integral
g RealFloat
s String
Which will works for basic types, but I don't know how it behaves for Showable
types.
It needs the last version of base
and GHC 7.8
so I can' try it.
Upvotes: 2
Reputation: 1739
1 - You're using explicit recursion - nothing wrong with that but there are functions out there that already do this for you. Example using forM_
:
import Control.Monad
import Text.Printf
bs l = forM_ (zip [1..] l) $ \(i, e) ->
putStrLn $ printf "%d. %s" (i :: Int) $ show e
2 - It might be possible to do that using typeclass wizardry, but it's too much involved for a beginner.
3 - I like to split separate side-effects so that I can still use the function in a pure setting. In this example it could have been done like this:
bs :: Show a => [a] -> String
bs l = concatMap f (zip [1..] l)
where f (i, e) = printf "%d. %s\n" (i :: Int) $ show e
putStr $ bs [1..10]
Upvotes: 3
Reputation: 1134
There is also Text.Printf
, if you're nostalgic for printf
:
import Text.Printf
bs :: Show a => [a] -> IO ()
bs = sequence_ . zipWith (\n x -> printf "%d. %s\n" n (show x)) [(1 :: Int)..]
Or if you don't want to use printf
:
bs xs = sequence_ $ zipWith (\n x -> mapM_ putStr [show n, ". ", show x, "\n"]) [1..] xs
Neither of these is very idiomatic, I think most people would make a pure function that returns a string and then print that when necessary:
bs' xs = unlines $ zipWith (\n x -> show n ++ ". " ++ show x) [1..] xs
bs xs = putStr (bs' xs)
Upvotes: 5