在Haskell中编号输出行

时间:2014-07-18 11:56:53

标签: haskell

我想显示具有任意类型的列表的内容,每行一个元素,从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
  1. 由于我是Haskell的绝对初学者,我想知道解决这个问题的“最佳”方法是什么?我是在正确的道路上还是只是简单的“坏”Haskell。

  2. 如何在没有''的String示例中输出Chars,并且仍然能够输出任何具有Show实例的类型?

  3. 我想从不同的角度了解解决此任务的其他方法,例如:可读性,效率,代码重用。

  4. 我也是这样做的,发现它甚至更奇怪(但不知何故很酷):

    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方法时编号出错了。在阅读完所有答案之后我很确定,即使这样,解决办法就是首先过滤列表然后压缩输出函数。

4 个答案:

答案 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类型的行为方式。 它需要最后一个版本的baseGHC 7.8,所以我可以'尝试一下。