代码之家  ›  专栏  ›  技术社区  ›  danatel

我的第一个haskell项目有什么改进?

  •  5
  • danatel  · 技术社区  · 15 年前

    这是我的第一个哈斯克尔计划。你会以更好的方式写哪些部分?

    -- 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 回复  |  直到 13 年前
        1
  •  11
  •   Norman Ramsey    15 年前

    以下是一些建议:

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

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

    • Haskell是高阶的,几乎所有的计算都与乘法无关。所以我要换个名字 可食性的 binopTable 并将实际乘法作为参数传入。

    • 最后,单个数字的格式是重复的。为什么不通过 \x -> format x b w 作为参数,无需 b w ?

    我建议的更改的净效果是,您构建了一个通用的高阶函数,用于为二进制运算符创建表。它的类型变得像

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

    最后得到一个更可重用的函数,例如,布尔真值表应该是小菜一碟。

    更高的顺序和可重用性 是哈斯克尔路。

        2
  •  11
  •   jrockway    15 年前

    你不使用任何来自 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是懒惰的,所以这减少了堆栈空间(也可以在无限列表上工作)。
    也, unwords unlines 是的快捷方式 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 ?

        3
  •  5
  •   yairchu    15 年前

    诺曼·拉姆齐提出了优秀的高水平(设计)建议;下面是一些低水平建议:

    • 首先,咨询 HLint .HLint是一个友好的程序,它为您提供关于如何改进haskell代码的基本建议!
      • 在您的案例中,hlint给出了7条建议。(主要是关于冗余支架)
      • 根据hlint的建议修改您的代码,直到它喜欢您提供的内容。
    • 更多类似的东西:
      • concat (replicate i "-") . 为什么不呢? replicate i '-' ?
    • 商榷 Hoogle 只要有理由相信您需要的函数已经在Haskell的库中可用。haskell有很多有用的功能,所以hoogle应该经常派上用场。
      • 需要连接字符串吗?寻找 [String] -> String 你发现了什么? concat . 现在去换掉所有的褶皱。
      • 先前的搜索也建议 unlines . 实际上,这更适合你的需要。太神奇了!
    • 可选:暂停并衷心感谢Neil M制作的Hoogle和HLint,感谢其他人制作的其他好东西,如Haskell、Bridges、网球和环卫设备。
    • 现在,对于每一个采用同一类型的几个参数的函数,通过给它们提供描述性的名称,明确哪些是什么意思。这比注释更好,但您仍然可以同时使用这两种方法。

    所以

    -- 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 在哪里? 不,你应该。
      • 如果你找到了 在哪里? 嵌套在另一个子句中的子句 在哪里? 条款有问题,那么我建议你改变缩进习惯。我的建议是始终使用2个或4个空格的一致缩进。然后你可以很容易地看到,在任何地方, 在哪里? 在另一个 在哪里? 是在。好啊

    下面是它的外观(样式还有一些其他变化):

    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) ' '
    
        4
  •  4
  •   sastanin    15 年前

    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 .

    同时 希尔特 这里没有特别的建议(只有一些多余的括号)。 GHC 会告诉你你实际上不需要 Text.Printf 在这里。。。

    2)尝试运行它时base=1或base=0或base=-1

        5
  •  2
  •   Jonno_FTW    15 年前

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

    {-  A multiline
       comment -}
    

    同样,永远不要使用 foldl 使用 foldl' 相反,在处理必须折叠的大列表的情况下。它更节省内存。

        6
  •  1
  •   Dan    15 年前

    一个简短的注释说明每个函数的功能、参数和返回值总是很好的。我必须非常仔细地阅读代码才能完全理解它。

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

    一个小警告:如果删除类型签名,您将自动获得多态性 Integral 支持上面提到的临时工,但你仍然需要一个 toAlphaDigits 因为臭名昭著的“单态限制”。