объединение ввода getLine с массивом haskell вызывает ошибку типа

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

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

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


person sion madisson    schedule 25.01.2020    source источник
comment
Как вы думаете, что делает students ++ studentArray?   -  person Joseph Sible-Reinstate Monica    schedule 25.01.2020
comment
По определению students ++ studentArray это то же самое, что и [] ++ studentArray — вы можете написать это вместо этого, и ничего не изменится. Точно так же последняя строка эквивалентна print []. Это явно не то, что вам нужно: вы пытаетесь мутировать переменную в Haskell, которая была разработана, чтобы помешать вам выполнить мутацию. Вам нужно переписать свой код, следуя чисто функциональному, а не императивному подходу.   -  person chi    schedule 25.01.2020


Ответы (1)


Во-первых, вы можете взглянуть на ресурсы в этом ответе 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