簡體   English   中英

將 getLine 輸入與 haskell 數組連接會引發類型錯誤

[英]concatenate getLine input with haskell array throws a type error

我完全是 Haskell 的初學者,我來自 js 環境,我有一個簡單的數組students ,我想一些學生對象送到其中,但遺憾的是 Haskell 不支持對象(如果有辦法可以做到,請指導我)所以試圖制作一個簡單的程序來讀取用戶輸入(數組)並將其送到students數組中,這是我嘗試過的:

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

但拋出以下錯誤: Couldn't match type `[]' with `IO'

首先,您可能需要查看此 SO answer中的資源。 如果您還沒有完成“絕對初學者”部分中的教程,那將是一個很好的起點。

看,對於其他編程語言,通常從打印"Hello, world!"程序開始"Hello, world!" 在屏幕上或者——就像你的例子一樣——從控制台讀取學生名單並將它們打印出來。 對於 Haskell 來說,首先使用完全不同類型的程序通常更有意義。 例如,教程“Learn You a Haskell for Great Good”不會變成"Hello, world!" 直到第 9 章, “快樂學習 Haskell 教程”直到第 15 章才開始(然后它只涵蓋輸出——輸入直到第 20 章才出現)。

無論如何,回到你的例子。 問題出在students ++ studentArray 這是一個將空列表studentArray students = []studentArray的值連接起來的表達式,該值是由getLine檢索到的String 由於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 期望此do塊中的所有(non- let )行都是 I/O 操作:

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-block 中的字符列表,Haskell 期望IO something但發現了[something] ,並且它抱怨列表 ( [] ) 和IO不匹配。

但是,即使您可以解決此問題,也無濟於事,因為與 JavaScript +運算符一樣,但與 JavaScript push方法不同的是,Haskell ++運算符不會修改其參數,因此a ++ b僅返回ab不改變ab

這是 Haskell 的一個非常基本的方面,使其與大多數其他編程語言不同。 默認情況下,Haskell 變量是不可變的。 一旦在頂層通過let語句分配它們,或者在函數調用中作為參數分配,它們就不會改變值。 (事實上​​,因為它們並不是真正的“變量”,我們通常稱它們為“綁定”而不是“變量”。)所以,如果你想在 Haskell 中建立一個students列表,你不要從分配開始一個變量的空列表,然后嘗試通過添加學生來修改該變量。 相反,您要么一次性完成所有操作:

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最初叫學生總數和初始空列表(獲取綁定到nstudentsSoFar分別在getStudents調用),然后使用遞歸來重新綁定nstudentsSoFar遞減n而“推”更多的學生上到了studentsSoFar

就其本身而言, studentsSoFar ++ [student]表達式不會做任何事情,但是通過在遞歸getStudents調用中使用它,這個新值可以重新綁定為studentsSoFar以模擬更改這個“變量”的值。

無論如何,這是 Haskell 中非常標准的方法,但對於來自 JavaScript 或其他語言的人來說可能不尋常,因此值得學習涵蓋輸入/輸出之前遞歸的教程......比如“學習你”(第 5 章中的遞歸) ,第 9 章中的 I/O)或“​​快樂學習”(第 10 章中的遞歸,第 15 和 20 章中的 I/O)或“Haskell 編程第一原則” (第 8 章中的遞歸,第 29 章中的 I/O)或“在 Haskell 中編程” (第 6 章遞歸,第 10 章 I/O)。 我相信你看到這里的模式。

暫無
暫無

聲明:本站的技術帖子網頁,遵循CC BY-SA 4.0協議,如果您需要轉載,請注明本站網址或者原文地址。任何問題請咨詢:yoyou2525@163.com.

 
粵ICP備18138465號  © 2020-2024 STACKOOM.COM