[英]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
僅返回a
和b
不改變a
或b
。
這是 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
最初叫學生總數和初始空列表(獲取綁定到n
和studentsSoFar
分別在getStudents
調用),然后使用遞歸來重新綁定n
和studentsSoFar
遞減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.