Что может быть изменено к лучшему моя первая haskell программа?

Вот моя первая программа 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 " " 
5
задан Don Stewart 18 April 2011 в 18:39
поделиться

6 ответов

Вот несколько предложений:

  • Чтобы сделать табличность вычислений более очевидной, я бы передал список [0..n] в функцию строки , а не передал бы n.

  • Далее я бы разделил вычисления горизонтальной и вертикальной осей так, чтобы они передавались в качестве аргументов в mulTable, а не вычислялись там.

  • Хаскелл выше по порядку, и почти ни одно из вычислений не имеет отношения к умножению. Поэтому я бы изменил имя mulTable на binopTable и передал бы действительное умножение в качестве параметра.

  • Наконец, форматирование отдельных чисел повторяется. Почему бы не передать \x -> формат x b w в качестве параметра, устраняя необходимость в b и w?

Чистый эффект предлагаемых мною изменений заключается в том, что вы строите общую функцию более высокого порядка для создания таблиц для двоичных операторов. Ее тип становится чем-то вроде

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

и вы получаете гораздо более многократно используемую функцию - например, таблицы булевой истины должны быть куском пирога.

Вышестоящий и многократно используемый - это Путь Хаскелла.

.
11
ответ дан 18 December 2019 в 05:31
поделиться

Вы ничего не используете из импорта Text.Printf.

Стилистически, вы используете больше скобок, чем необходимо. Хаскеллеры склонны находить код более читабельным, когда он очищен от посторонних вещей. Вместо чего-то вроде h x = f (g x), напишите h = f . g.

Ничто здесь действительно не требует Int; (Интеграл a) => a должен делать.

фолд (++) x xs == concat $ x : xs: Надеюсь, встроенный concat будет работать лучше, чем ваша реализация.
. Также вы должны предпочесть foldr, когда функция ленива во втором аргументе, так как (++) - так как Хаскелл ленивый, это уменьшает пространство стека (а также работает на бесконечных списках).
. Кроме того, unwords и unlines являются ярлыками для intercalate " и concat . карты (++ "\n"), т.е. соответственно. "join with spaces" и "join with newline (plus trailing newline)"; вы можете заменить пару вещей на эти.

Если вы не используете большие числа, w = длина $ takeWhile (<= n) $ итерация (* b) 1 вероятно быстрее. Или, в случае ленивого программиста, пусть w = длина $ toBase b n.

concat ( (реплицировать ((w+1)* (n+2)) "-" ) == реплицировать ((w+1) * (n+2)) '-' - не знаю, как вы это пропустили, вы правильно поняли только пару строк.

То же самое вы делаете и с concat пробелами. Однако, не проще ли было бы на самом деле использовать Text.Printf импорт и написать printf "%*s" w digits?

.
11
ответ дан 18 December 2019 в 05:31
поделиться

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
ответ дан 18 December 2019 в 05:31
поделиться

Если вы хотите, чтобы многострочные комментарии использовали:

{-  A multiline
   comment -}

Также, никогда не используйте фолдл, вместо этого используйте фолдл', в случаях, когда вы имеете дело с большими списками, которые должны быть сложены. Это более эффективно с точки зрения экономии памяти.

2
ответ дан 18 December 2019 в 05:31
поделиться

Краткий комментарий о том, что делает каждая функция, ее аргументы и возвращаемое значение, всегда хорош. Мне пришлось прочитать код довольно внимательно, чтобы полностью разобраться в нем.

Кто-то скажет, что если это сделать, то явных сигнатур типа может и не потребоваться. Это эстетический вопрос, у меня нет на него сильного мнения.

Одно небольшое предостережение: если вы удалите знаки типа, то автоматически получите упомянутую полиморфную Integral поддержку эфемента, но она все равно понадобится около toAlphaDigits из-за печально известного "ограничения мономорфизма".

.
1
ответ дан 18 December 2019 в 05:31
поделиться

Норман Рэмзи дал отличные предложения высокого уровня (дизайна); Ниже приведены некоторые низкоуровневые:

  • Сначала проконсультируйтесь с HLint. HLint - дружественная программа, которая дает простые советы по улучшению кода Haskell!
    • В вашем случае HLint дает 7 советов. (в основном о лишних скобках)
    • Модифицируйте свой код в соответствии с предложениями HLint до тех пор, пока ему не понравится то, что Вы ему подаете.
  • Больше похожих на HLint вещей:
    • concat (реплицируйте i "-"). Почему бы не не реплицировать i '-'?
  • Проконсультируйтесь с Hoogle всякий раз, когда есть основания полагать, что нужная вам функция уже доступна в библиотеках Хаскелла. Haskell поставляется с множеством полезных функций, поэтому Hoogle должен пригодиться довольно часто.
    • Нужно конкатенировать строки? Ищите [Строка] -> Строка, и вуаля вы нашли concat. Теперь идите и замените все эти складки.
    • Предыдущий поиск также предложил unlines. Вообще-то, это даже лучше подходит для ваших нужд. Это волшебство!
  • Необязательно: сделайте паузу и поблагодарите в сердце Neil M за создание Hoogle и HLint, и поблагодарите других за создание других хороших вещей, таких как Хаскелл, мосты, теннисные мячи и санитария.
  • Теперь, для каждой функции, которая принимает несколько аргументов одного и того же типа, сделайте ясным, что означает что, дав им описательные имена. Это лучше, чем комментарии, но можно использовать и то, и другое.

Так

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

становится

mulTable :: Int -> Int -> String
mulTable size base =
  • Чтобы смягчить удар по лишним символам предыдущего предложения: Когда функция используется только один раз и не очень полезна сама по себе, поместите ее в область видимости вызывающего абонента в пункте где , где она может использовать переменные вызывающих абонентов, избавляя вас от необходимости передавать ей все.

Так

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
  • Вы даже можете переместить строку в mulTable где пункт; imho, вы должны это сделать.
    • Если вы обнаружили, что пункт , где вложен в другой пункт , где вызывает беспокойство, то я предлагаю изменить ваши привычки делать отступы. Моя рекомендация заключается в использовании последовательного отступа всегда в 2 или всегда в 4 пробела. Тогда вы сможете легко увидеть везде, где находится , где в другой , где . ok

Ниже представлено, как это выглядит (с некоторыми другими изменениями в стиле):

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) ' '
5
ответ дан 18 December 2019 в 05:31
поделиться
Другие вопросы по тегам:

Похожие вопросы: