объединение ввода getLine с массивом haskell выдает ошибку типа - PullRequest
0 голосов
/ 25 января 2020

я полностью новичок в Haskell я пришел из js среды, у меня есть простой массив students, в который я хочу pu sh некоторые студенческие объекты, но, к сожалению Haskell делает не поддерживает объекты (если есть способ, которым я могу это сделать, пожалуйста, наставьте меня), поэтому попытался создать простую программу, которая считывает пользовательский ввод (массив) и pu sh, что в массив students вот что я пробовал:

main :: IO()
main = do 
  let students = []
  studentArray <- getLine
  students ++ studentArray
  print(students)

но выдается следующая ошибка: Couldn't match type `[]' with `IO'

1 Ответ

7 голосов
/ 25 января 2020

Во-первых, вы можете взглянуть на ресурсы в этом SO-ответе . Если вы еще не работали с учебниками в разделе «Абсолютный новичок», это было бы хорошей отправной точкой.

См., Что для других языков программирования, как правило, начинать с программ, которые печатают "Hello, world!" на экране или что - как ваш пример - прочитайте списки студентов из консоли и распечатайте их обратно. Для Haskell обычно имеет смысл сначала работать с совершенно разными типами программ. Например, учебник «Учим тебя Haskell для великого блага» не достигает "Hello, world!" до главы 9, а «Учеба для счастливого обучения Haskell» не доходит до главы 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 do-блоке, Haskell ожидал IO something, но обнаружил [something], и жалуется, что типы списков ([]) и IO не совпадают.

Но даже если бы вы могли это исправить, это не помогло бы, потому что, как оператор JavaScript + и в отличие от JavaScript push метод, оператор Haskell ++ не изменяет свои аргументы, поэтому a ++ b возвращает только объединение a и b без изменения a или b.

Это довольно фундаментальный аспект Haskell, который отличает его от большинства других языков программирования. По умолчанию переменные Haskell являются неизменяемыми. Как только они назначаются на верхнем уровне оператором let или назначаются в качестве аргументов при вызове функции, они не меняют значение. (На самом деле, поскольку они на самом деле не являются «переменными», мы обычно называем их «привязками» вместо «переменных».) Итак, если вы хотите создать список students в Haskell, вы не t начать с присвоения переменной пустого списка, а затем попытаться изменить эту переменную, добавив студентов. Вместо этого вы либо делаете все сразу:

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])

Смотрите здесь как * Изначально вызывается 1056 * с общим количеством студентов и начальным пустым списком (которые связываются с n и studentsSoFar соответственно в вызове getStudents), а затем использует рекурсию для повторного связывания n и studentsSoFar, чтобы уменьшить n, в то же время "подталкивая" больше учеников к studentsSoFar.

Само по себе выражение studentsSoFar ++ [student] ничего не даст, но используя его в рекурсивном вызове getStudents, это новое значение может быть переопределено как studentsSoFar для имитации изменения значения этой «переменной».

В любом случае, это довольно стандартный подход в Haskell, но, возможно, он необычен для людей из JavaScript или других языков, так что стоит поработать над учебными пособиями, которые охватывают рекурсию перед вводом / выводом ... например, «Learn You» (рекурсия в главе 5, ввод-вывод в главе 9) или «Happy Learn» (рекурсия в главе 10 , Ввод / вывод в главах 15 и 20) или "Haskell Программирование из первых принципов" (рекурсия в главе 8, ввод / вывод в главе 29) или "Программирование в Haskell" (рекурсия в Глава 6, ввод / вывод в главе 10). Я уверен, что вы видите образец здесь.

...