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

dan*_*tel 5 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 " " 
Run Code Online (Sandbox Code Playgroud)

eph*_*ent 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吗?


Nor*_*sey 11

以下是一些建议:

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

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

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

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

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

binopTable :: (i -> String) -> (i -> i -> i) -> [i] -> [i] -> String
Run Code Online (Sandbox Code Playgroud)

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

高阶和​​可重用是Haskell方式.


yai*_*chu 5

诺曼拉姆齐提出了出色的高级(设计)建议; 以下是一些低级别的:

  • 首先,咨询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 =
Run Code Online (Sandbox Code Playgroud)

mulTable :: Int -> Int -> String
mulTable size base =
Run Code Online (Sandbox Code Playgroud)
  • 软化前一个建议的额外字符:当一个函数只使用一次,并且它本身不是很有用时,将它放在其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
Run Code Online (Sandbox Code Playgroud)

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
Run Code Online (Sandbox Code Playgroud)
  • 你甚至可以line进入mulTable's where条款; imho,你应该.
    • 如果你发现一个where嵌套在另一个where条款中的条款令人不安,那么我建议改变你的缩进习惯.我的建议是使用始终为2或总是4个空格的一致缩进.然后,您可以轻松地在任何地方看到where另一个where所在的位置.好

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

import Data.List
import Data.Char

mulTable :: Int -> Int -> String
mulTable size base =
  unlines $
  [ vertHeaders
  , minusSignsLine
  ] ++ map line [0 .. size]
  where
    vertHeaders =
      concat
      $ replicate (cellWidth + 2) ' '
      : map horizontalHeader [0 .. size]
    horizontalHeader i = format i base cellWidth
    minusSignsLine = replicate ((cellWidth + 1) * (size + 2)) '-'
    cellWidth = length $ toBase base (size * size)
    line y =
      concat
      $ format y base cellWidth
      : "|"
      : map element [0 .. size]
      where
        element x = format (y * x) base cellWidth

toBase :: Integral i => i -> i -> [i]
toBase base
  = reverse
  . map (`mod` base)
  . takeWhile (> 0)
  . iterate (`div` base)

toAlphaDigit :: Int -> Char
toAlphaDigit n
  | n < 10    = chr (n + ord '0')
  | otherwise = chr (n + ord 'a' - 10)

format :: Int -> Int -> Int -> String
format v b w =
  spaces ++ digits ++ " "
  where 
    digits
      | v == 0    = "0"
      | otherwise = map toAlphaDigit (toBase b v)
    spaces = replicate (w - length digits) ' '
Run Code Online (Sandbox Code Playgroud)