Во-первых, вы можете взглянуть на ресурсы в этом ответе SO. Если вы еще не работали с учебными пособиями в разделе «Абсолютный новичок», это будет хорошей отправной точкой.
Видите ли, для других языков программирования обычно начинают с программ, которые выводят "Hello, world!"
на экран или, как в вашем примере, считывают с консоли списки учеников и распечатывают их обратно. Для Haskell обычно имеет смысл сначала работать с совершенно разными типами программ. Например, учебник "Изучай Haskell во благо" не доходит до "Hello, world!"
до главы 9, а "Happy Learn Haskell Tutorial" не рассматривается до главы 15 (а затем он касается только вывода -- ввода не приходит до главы 20).
Впрочем, вернемся к вашему примеру. Проблема со строкой students ++ studentArray
. Это выражение объединяет пустой список students = []
со значением studentArray
, которое представляет собой String
, полученное getLine
. Поскольку String
— это просто список символов, пустой список — это просто пустая строка, поэтому вы пишете грубый эквивалент функции JavaScript:
function main() {
var students = "" // empty list is just empty string
var studentArray = readLineFromSomewhere()
students + studentArray // concatenate strings and throw away result
console.log(students) // print the empty string
}
В JavaScript это запустится и напечатает пустую строку, потому что строка students + studentArray
ничего не делает. В Haskell это не проверяет тип, потому что Haskell ожидает, что все строки (не let
) в этом блоке do
будут действиями ввода-вывода:
main :: IO () -- signature forces `do` block to be I/O
main = do
let students = [] -- "let" line is okay
studentArray <- getLine -- `getLine` is IO action
students ++ studentArray -- **NOT** IO action: it's a String AKA [Char]
print students -- `print students` is IO action
Поскольку students ++ studentArray
представляет собой список String
/ [Char]
/ символов, появляющихся в блоке выполнения IO
, Haskell ожидал IO something
, но нашел [something]
и жалуется, что типы списков ([]
) и IO
не совпадают.
Но даже если бы вы могли исправить это, это не помогло бы, потому что, подобно оператору JavaScript +
и в отличие от метода JavaScript push
, оператор Haskell ++
не изменяет свои аргументы, поэтому a ++ b
возвращает только конкатенацию a
и b
без изменение a
или b
.
Это довольно фундаментальный аспект Haskell, отличающий его от большинства других языков программирования. По умолчанию переменные Haskell неизменяемы. После того, как они назначены на верхнем уровне оператором let
или назначены в качестве аргументов в вызове функции, они не изменяют значение. (На самом деле, поскольку они на самом деле не являются «переменными», мы обычно называем их «привязками», а не «переменными».) Итак, если вы хотите создать список students
в Haskell, вы не начинаете с присваивания пустой список в переменную, а затем пытается изменить эту переменную, добавляя студентов. Вместо этого вы либо делаете все сразу:
import Control.Monad (replicateM)
main :: IO ()
main = do
putStrLn "Enter number of students:"
n <- readLn
putStrLn $ "Enter " ++ show n ++ " student names:"
students <- replicateM n getLine
putStrLn $ "List of students:"
print students
или используйте вызовы функций для имитации переменных путем повторной привязки идентификатора к обновленному значению:
main :: IO ()
main = do
putStrLn "Enter number of students:"
n <- readLn
putStrLn $ "Enter " ++ show n ++ " student names:"
students <- getStudents n []
print students
getStudents :: Int -> [String] -> IO [String]
getStudents 0 studentsSoFar = return studentsSoFar
getStudents n studentsSoFar = do
student <- getLine
getStudents (n-1) (studentsSoFar ++ [student])
Посмотрите здесь, как getStudents
первоначально вызывается с общим количеством студентов и начальным пустым списком (который привязывается к n
и studentsSoFar
соответственно в вызове getStudents
), а затем использует рекурсию для повторной привязки n
и studentsSoFar
для уменьшения n
при "выталкивании " больше студентов на studentsSoFar
.
Само по себе выражение studentsSoFar ++ [student]
ничего не даст, но, используя его в рекурсивном вызове getStudents
, это новое значение можно повторно связать как studentsSoFar
, чтобы имитировать изменение значения этой "переменной".
В любом случае, это довольно стандартный подход в Haskell, но он может быть необычным для людей, перешедших с JavaScript или других языков, поэтому стоит изучить учебники, которые охватывают рекурсию перед вводом/выводом... например, «Узнай себя» (рекурсия в главе 5). , ввод-вывод в главе 9) или "Happy Learn" (рекурсия в главе 10, ввод-вывод в главах 15 и 20) или " Программирование на Haskell с первых принципов" (рекурсия в главе 8, ввод-вывод в главе 29) или "Программирование на Haskell" (рекурсия в главе 6, ввод-вывод в главе 10). Я уверен, что вы видите здесь закономерность.
person
K. A. Buhr
schedule
25.01.2020
students ++ studentArray
? - person Joseph Sible-Reinstate Monica   schedule 25.01.2020students ++ studentArray
это то же самое, что и[] ++ studentArray
— вы можете написать это вместо этого, и ничего не изменится. Точно так же последняя строка эквивалентнаprint []
. Это явно не то, что вам нужно: вы пытаетесь мутировать переменную в Haskell, которая была разработана, чтобы помешать вам выполнить мутацию. Вам нужно переписать свой код, следуя чисто функциональному, а не императивному подходу. - person chi   schedule 25.01.2020