我想显示具有任意类型的列表的内容,每行一个元素,从1开始编号,如下所示:
字符串示例:
> bs "Hallo"
1. 'H'
2. 'a'
3. 'l'
4. 'l'
5. 'o'
整数示例
> bs [5,6,1,2]
1. 5
2. 6
3. 1
4. 2
元组示例
> bs [(4,"Test"),(3,"Aye"),(5,"Fives")]
1. (4,"Test")
2. (3,"Ayes")
3. (4,"Fives)
我发现这是一个解决方案:
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
由于我是Haskell的绝对初学者,我想知道解决这个问题的“最佳”方法是什么?我是在正确的道路上还是只是简单的“坏”Haskell。
如何在没有''的String示例中输出Chars,并且仍然能够输出任何具有Show实例的类型?
我想从不同的角度了解解决此任务的其他方法,例如:可读性,效率,代码重用。
我也是这样做的,发现它甚至更奇怪(但不知何故很酷):
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)
我做了大约25年的命令式编程,并且对学习新东西真的很感兴趣。同时如果对Haskell中的代码感到难以置信的“奇怪”,我仍然无法想象这个“疯狂的月亮语言”如何完成一个大项目:)
编辑:我要感谢大家。我选择一个答案,因为我必须,但所有人都非常有帮助!我还想说我的顶级解决方案是因为“在真正的问题中”来自我必须跳过一些列表元素并且使用zip方法时编号出错了。在阅读完所有答案之后我很确定,即使这样,解决办法就是首先过滤列表然后压缩输出函数。答案 0 :(得分:5)
如果您对Text.Printf
怀旧,还有printf
:
import Text.Printf
bs :: Show a => [a] -> IO ()
bs = sequence_ . zipWith (\n x -> printf "%d. %s\n" n (show x)) [(1 :: Int)..]
或者,如果您不想使用printf
:
bs xs = sequence_ $ zipWith (\n x -> mapM_ putStr [show n, ". ", show x, "\n"]) [1..] xs
这些都不是非常惯用的,我想大多数人会创建一个返回字符串然后在必要时打印的纯函数:
bs' xs = unlines $ zipWith (\n x -> show n ++ ". " ++ show x) [1..] xs
bs xs = putStr (bs' xs)
答案 1 :(得分:4)
将任务分解为小部分是很好的。在这种情况下,您希望1)通过显示元素并在数字前面添加数字来渲染每一行2.然后在终端中打印每个渲染。
所以渲染只是一些字符串修改:
renderLine :: Show a => Integer -> a -> String
renderLine i a = show i ++ ". " ++ show a
许多行的组合需要将连续的数字传递给渲染:
bs :: Show a => [a] -> String
bs = unlines . zipWith renderLine [1..]
这给我们的结果如下:
*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")
<强>问题强>
由于我是Haskell的绝对初学者,我想知道什么是“最好”的方式 解决这个问题?我是在正确的道路上还是只是平原 “坏”哈斯克尔。
我认为最好的方法是对于练习的Haskell程序员来说最干净的方法,这通常意味着使用Prelude中的常用函数,例如zipWith
,并尽可能避免手动原始递归。 / p>
如何在没有''和静止的字符串示例中输出字符 能够输出任何具有Show?
实例的类型
要使用相同的功能对不同类型执行不同的操作,您需要一个类型类。
编辑我没有仔细阅读。我现在看到你想要为Show
。
有很多很长的答案可以(也可能会)在这里给出关于如何使Char
单向运行但是有效地提升所有其他Show
实例会留下编译器必须解决的一些歧义。我会跳过这个,只是告诉您我们需要通过您在下面看到的{-# LANGUAGE ... #-}
编译语对该语言进行多次扩展:
{-# 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
在实践中:
*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
答案 2 :(得分:3)
1 - 您正在使用显式递归 - 没有任何问题,但有些功能已经为您做了这个。使用forM_
的示例:
import Control.Monad
import Text.Printf
bs l = forM_ (zip [1..] l) $ \(i, e) ->
putStrLn $ printf "%d. %s" (i :: Int) $ show e
2 - 使用类型类魔法可能会做到这一点,但对初学者来说太过分了。
3 - 我喜欢分开单独的副作用,这样我仍然可以在纯设置中使用该功能。在这个例子中,它可以像这样完成:
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]
答案 3 :(得分:2)
回答问题1:zip [1..]
或变体是实现它的方法。它完全意味着你想要的东西。
关于问题2:Show
的问题是它用于两个目的,打印事物和序列化事物。理论上,show
应与Read
相反,因此应包括从字符串重建数据所需的所有信息。所以show
为char和string添加引号和双引号,你无能为力。
你能做的是,
display
函数,用于删除引号和双引号(不应该太难
如果你正在学习哈斯克尔,这是一个很好的练习。printf
与%v
一起使用“%v”说明符是为所有内置类型提供的,应该是 也提供用户定义的类型格式化程序。它选择了“最好的” 给定类型的表示。对于内置类型“%v” 说明符转换如下:
c Char
你的其他无符号积分
d其他签名的积分
g RealFloat
s String
哪个适用于基本类型,但我不知道它对Showable
类型的行为方式。
它需要最后一个版本的base
和GHC 7.8
,所以我可以'尝试一下。