Вот моя первая программа 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 " "
Вот несколько предложений:
Чтобы сделать табличность вычислений более очевидной, я бы передал список [0..n]
в функцию строки
, а не передал бы n
.
Далее я бы разделил вычисления горизонтальной и вертикальной осей так, чтобы они передавались в качестве аргументов в mulTable
, а не вычислялись там.
Хаскелл выше по порядку, и почти ни одно из вычислений не имеет отношения к умножению. Поэтому я бы изменил имя mulTable
на binopTable
и передал бы действительное умножение в качестве параметра.
Наконец, форматирование отдельных чисел повторяется. Почему бы не передать \x -> формат x b w
в качестве параметра, устраняя необходимость в b
и w
?
Чистый эффект предлагаемых мною изменений заключается в том, что вы строите общую функцию более высокого порядка для создания таблиц для двоичных операторов. Ее тип становится чем-то вроде
binopTable :: (i -> String) -> (i -> i -> i) -> [i] -> [i] -> String
и вы получаете гораздо более многократно используемую функцию - например, таблицы булевой истины должны быть куском пирога.
Вышестоящий и многократно используемый - это Путь Хаскелла.
. Вы ничего не используете из импорта 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
?
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
.Если вы хотите, чтобы многострочные комментарии использовали:
{- A multiline
comment -}
Также, никогда не используйте фолдл
, вместо этого используйте фолдл'
, в случаях, когда вы имеете дело с большими списками, которые должны быть сложены. Это более эффективно с точки зрения экономии памяти.
Краткий комментарий о том, что делает каждая функция, ее аргументы и возвращаемое значение, всегда хорош. Мне пришлось прочитать код довольно внимательно, чтобы полностью разобраться в нем.
Кто-то скажет, что если это сделать, то явных сигнатур типа может и не потребоваться. Это эстетический вопрос, у меня нет на него сильного мнения.
Одно небольшое предостережение: если вы удалите знаки типа, то автоматически получите упомянутую полиморфную Integral
поддержку эфемента, но она все равно понадобится около toAlphaDigits
из-за печально известного "ограничения мономорфизма".
Норман Рэмзи дал отличные предложения высокого уровня (дизайна); Ниже приведены некоторые низкоуровневые:
concat (реплицируйте i "-")
. Почему бы не не реплицировать i '-'
? [Строка] -> Строка
, и вуаля вы нашли concat
. Теперь идите и замените все эти складки.unlines
. Вообще-то, это даже лучше подходит для ваших нужд. Это волшебство!Так
-- 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) ' '