Первое, что нужно сделать, крайне важный первый принцип, - это извлечь как можно больше мыслей из main
или из IO
.main
должен по возможности содержать все IO
и, возможно, только IO
, украшенные чистыми терминами, которые вы определили в другом месте модуля.Ваш getLines
смешивает их без необходимости.
Итак, чтобы убрать это с пути, мы должны иметь main
, что-то вроде
main =
do putStrLn "What is your name?"
name <- getContents
names <- readFile "names.txt"
putStrLn (frankJ name names)
- или, возможно, более строгую сегрегацию IO
от всехиначе мы получаем из:
main =
do putStrLn greeting
name <- getContents
names <- readFile nameFile
putStrLn (frankJ name names)
вместе с «чистыми» терминами:
greeting, nameFile :: String
greeting = "What is your name?"
nameFile = "names.txt"
В любом случае, мы сейчас действительно на земле Хаскелла: теперь проблема в том, чтобы понятьчто такое чистая функция :
frankJ :: String -> String -> String
.
Мы могли бы начать с простой функции сопоставления: мы получаем совпадение, когда первая строка появляется в списке строк:
match :: String -> [String] -> Bool
match name namelist = name `elem` namelist
-- pretty clever, that!
или мы можем захотеть немного нормализовать, так чтопробел в начале и конце имени, которое нам дано, и имена в списке не влияют на совпадение.Вот довольно потрепанный способ сделать это:
clean :: String -> String
clean = reverse . omitSpaces . reverse . omitSpaces
where omitSpaces = dropWhile (== ' ')
Тогда мы можем улучшить наш старый match
, то есть elem
:
matchClean :: String -> [String] -> Bool
matchClean name namelist = match (clean name) (map clean namelist)
Теперь нам нужно следовать типамвыяснить, как согласовать тип, скажем, matchClean:: String -> [String] -> Bool
с типом frankJ :: String -> String -> String
.Мы хотим вписать его в наше определение frankJ
.
Таким образом, чтобы «обеспечить ввод» для matchClean
, нам нужна функция, которая выводит нас из длинной строки с символами новой строки в список строк (имена), которые нужны matchClean
: это функция Prelude lines
.
Но мы также должны решить, что делать с Bool
, который matchClean
дает в качестве значения;frankJ
, как мы имеем, возвращает String
.Давайте продолжим простую декомпозицию задачи:
response :: Bool -> String
response False = "We're sorry, your name does not appear on the list, please leave."
response True = "Hey, you're on the A-list, welcome!"
Теперь у нас есть материалы, которые мы можем составить в разумного кандидата на функцию frankJ :: String -> String -> String
, которую мы подаем в нашу машину IO
, определенную вmain
:
frankJ name nametext = response (matchClean name (lines nametext))
-- or maybe the fancier:
-- frankJ name = response . matchClean name . lines
-- given a name, this
-- - pipes the nametext through the lines function, splitting it,
-- - decides whether the given name matches, and then
-- - calculates the 'response' string
Таким образом, здесь почти все является вопросом чистых функций, и легко увидеть, как исправить вещи для дальнейшего совершенствования.Например, возможно, введенное имя и строки текстового файла должны быть дополнительно нормализованы.Внутренние пробелы должны быть ограничены одним пробелом перед сравнением.Или, возможно, в списке есть запятая, поскольку люди указаны как «фамилия, имя» и т. Д. И т. Д. Или, может быть, мы хотим, чтобы функция ответа использовала имя человека:
personalResponse :: String -> Bool -> String
personalResponse name False = name ++ " is a loser, as far as I can tell, get out!"
personalResponse name True = "Ah, our old friend " ++ name ++ "! Welcome!"
вместе с
frankJpersonal name = personalResponse name . matchClean name . lines
Конечно, есть миллион способов решения этой проблемы.Например, есть regex
библиотеки.Отличный и простой Data.List.Split
от Hackage также может быть полезен, но я не уверен, что он может быть использован Hugs, который вы могли бы использовать.
Замечу, что вы используете устаревшие имена для импортированных модулей.То, что я написал, использует только Prelude, поэтому в импорте нет необходимости, но другие модули теперь называются «System.IO», «Data.List» и «Control.Monad» в соответствии с иерархической системой именования.Интересно, используете ли вы старый учебник или руководство.Может быть, приятный сайт «Learn You a Haskell» будет лучше?Он подтверждает, что использует ghc
, но я думаю, что это не сильно повлияет.