我的第一个haskell计划可以改进什么?

时间:2009-12-28 17:49:30

标签: haskell coding-style

这是我的第一个Haskell程序。你会用更好的方式写些什么部分?

-- Multiplication table
-- Returns n*n multiplication table in base b

import Text.Printf
import Data.List
import Data.Char

-- Returns n*n multiplication table in base b 
mulTable :: Int -> Int -> String
mulTable n b = foldl (++) (verticalHeader n b w) (map (line n b w) [0..n])
               where 
                 lo = 2* (logBase (fromIntegral  b)  (fromIntegral n))
                 w = 1+fromInteger (floor lo)

verticalHeader :: Int -> Int -> Int -> String  
verticalHeader n b w = (foldl (++) tableHeader columnHeaders)
                        ++ "\n" 
                        ++ minusSignLine 
                        ++ "\n"
                   where
                     tableHeader = replicate (w+2) ' '
                     columnHeaders = map (horizontalHeader b w) [0..n]
                     minusSignLine = concat ( replicate ((w+1)* (n+2)) "-" )

horizontalHeader :: Int -> Int -> Int -> String
horizontalHeader b w i = format i b w

line :: Int -> Int -> Int -> Int -> String
line n b w y  = (foldl (++) ((format y b w) ++ "|" ) 
                           (map (element b w y) [0..n])) ++ "\n"

element :: Int -> Int -> Int -> Int -> String  
element b w y x = format  (y * x) b w

toBase :: Int -> Int -> [Int]
toBase b v = toBase' [] v where
  toBase' a 0 = a
  toBase' a v = toBase' (r:a) q where (q,r) = v `divMod` b

toAlphaDigits :: [Int] -> String
toAlphaDigits = map convert where
  convert n | n < 10    = chr (n + ord '0')
            | otherwise = chr (n + ord 'a' - 10)

format :: Int -> Int -> Int -> String
format v b w = concat spaces ++ digits ++ " "
               where 
                   digits  = if v == 0 
                             then "0" 
                             else toAlphaDigits ( toBase b v )
                   l = length digits
                   spaceCount = if (l > w) then  0 else (w-l) 
                   spaces = replicate spaceCount " " 

6 个答案:

答案 0 :(得分:11)

您不使用import Text.Printf中的任何内容。

从风格上讲,你使用的括号多于必要的括号。 Haskellers倾向于在清除那些无关紧要的东西时发现代码更具可读性。而不是h x = f (g x)之类的内容,请写h = f . g

这里真的不需要Int; (Integral a) => a应该这样做。

foldl (++) x xs == concat $ x : xs:我相信内置的concat比您的实施更有效。
此外,当函数在第二个参数中是惰性时,您应该更喜欢foldr,因为(++)是 - 因为Haskell是惰性的,这会减少堆栈空间(并且也可以在无限列表上工作)。 此外,unwordsunlines分别是intercalate " "concat . map (++ "\n")的快捷方式,即“与空格连接”和“与换行符连接(加上尾随换行符)”;你可以替换那些东西。

除非您使用大数字,否则w = length $ takeWhile (<= n) $ iterate (* b) 1可能更快。或者,对于懒惰的程序员,请w = length $ toBase b n

concat ( (replicate ((w+1)* (n+2)) "-" ) == replicate ((w+1) * (n+2)) '-' - 不确定你是怎么错过这一个的,你只是把它弄好了几行。

你也可以使用concat spaces做同样的事情。但是,实际使用Text.Printf导入并写printf "%*s " w digits

会不会更容易

答案 1 :(得分:11)

以下是一些建议:

  • 为了使计算的表格更明显,我将列表[0..n]传递给line函数,而不是传递n

  • 我会进一步拆分水平和垂直轴的计算,以便它们作为参数传递给mulTable而不是在那里计算。

  • Haskell是高阶的,几乎没有计算与乘法有关。因此,我会将mulTable的名称更改为binopTable,并将实际的乘法作为参数传递。

  • 最后,个别数字的格式是重复的。为什么不将\x -> format x b w作为参数传递,从而无需bw

我建议的更改的净效果是您构建了一个通用的高阶函数来为二元运算符创建表。它的类型就像

binopTable :: (i -> String) -> (i -> i -> i) -> [i] -> [i] -> String

你最终可以使用更多可重复使用的函数 - 例如,布尔真值表应该是小菜一碟。

高阶和可重用是Haskell方式。

答案 2 :(得分:5)

Norman Ramsey提供了出色的高级(设计)建议;以下是一些低级别的:

  • 首先,咨询HLint。 HLint是一个友好的程序,为您提供有关如何改进Has​​kell代码的基本建议!
    • 在您的情况下,HLint提供了7条建议。 (主要是冗余括号)
    • 根据HLint的建议修改您的代码,直到它喜欢您提供的内容为止。
  • 更像HLint的东西:
    • concat (replicate i "-")。为什么不replicate i '-'
  • 只要有理由相信您需要的功能已经在Haskell的库中可用,请咨询Hoogle。 Haskell带来了大量有用的功能,因此Hoogle应该经常派上用场。
    • 需要连接字符串吗?搜索[String] -> String,然后找到concat。现在去替换所有这些折叠。
    • 之前的搜索还建议unlines。实际上,这更适合您的需求。这太神奇了!
  • 可选:暂停并感谢Neil M制作Hoogle和HLint,感谢其他人制作Haskell,桥梁,网球和卫生等其他好东西。
  • 现在,对于每个采用相同类型的多个参数的函数,通过给它们描述性的名称,明确哪个意味着什么。这比评论更好,但你仍然可以使用它们。

所以

-- Returns n*n multiplication table in base b 
mulTable :: Int -> Int -> String
mulTable n b =

变为

mulTable :: Int -> Int -> String
mulTable size base =
  • 软化前一个建议的额外字符:当一个函数只使用一次,并且它本身不是很有用时,将它放在其where子句中的调用者范围内,在那里它可以使用呼叫者的变量,使您无需将所有内容传递给它。

所以

line :: Int -> Int -> Int -> Int -> String
line n b w y =
  concat
  $ format y b w
  : "|"
  : map (element b w y) [0 .. n]

element :: Int -> Int -> Int -> Int -> String  
element b w y x = format (y * x) b w

变为

line :: Int -> Int -> Int -> Int -> String
line n b w y =
  concat
  $ format y b w
  : "|"
  : map element [0 .. n]
  where
    element x = format (y * x) b w
  • 您甚至可以将line移至mulTable的{​​{1}}条款中; imho,你应该。
    • 如果你发现嵌套在另一个where条款中的where条款令人不安,那么我建议改变你的缩进习惯。我的建议是使用始终为2或总是4个空格的一致缩进。然后,您可以在任何地方轻松查看其他wherewhere所在的位置。确定

下面是它的样子(还有一些其他风格的变化):

where

答案 3 :(得分:4)

0)添加一个主要功能:-)至少基本

import System.Environment (getArgs)
import Control.Monad (liftM)

main :: IO ()
main = do
  args <- liftM (map read) $ getArgs
  case args of
    (n:b:_) -> putStrLn $ mulTable n b
    _       -> putStrLn "usage: nntable n base"

1)使用ghc运行runhaskell-Wall;贯穿hlint

虽然hlint在这里没有提出任何特别的建议(只有一些冗余括号),但ghc会告诉您实际上这里并不需要Text.Printf ...

2)尝试使用base = 1或base = 0或base = -1

运行它

答案 4 :(得分:2)

如果您想要多行注释,请使用:

{-  A multiline
   comment -}

另外,在您处理必须折叠的大型列表的情况下,永远不要使用foldl,而是使用foldl'。它的内存效率更高。

答案 5 :(得分:1)

简短的评论说明每个函数的作用,它的参数和返回值总是好的。我必须仔细阅读代码才能完全理解它。

有人会说,如果你这样做,可能不需要显式类型签名。这是一个美学问题,我对它没有强烈的意见。

一个小警告:如果你删除了类型签名,你将自动获得所提到的多态Integral支持ephemient,但由于臭名昭着的“单态限制”,你仍然需要toAlphaDigits左右的一个。“