IO в Haskell не работает как IO на языках, к которым вы привыкли.Все функции в Haskell должны быть чистыми: то есть, если функция f
вызывается с аргументом x
, не должно быть никакой разницы между вызовом ее один, два или сто раз.Подумайте, что это значит для IO.Наивно, getLine
должен иметь тип getLine :: String
или, возможно, getLine :: () -> String
.(()
- это тип единицы, единственным значением которого является ()
; он похож на тип void в C-подобном языке, но его значение одно). Но это будет означать, что каждый раз, когда вы пишетеgetLine
, он должен был бы вернуть ту же строку , что не то, что вы хотите.Это цель типа IO
: инкапсулировать действия .Эти действия отличаются от функций;они представляют собой нечистое вычисление (хотя сами они чисты).Значение типа IO a
представляет действие, которое при выполнении возвращает значение типа a
.Таким образом, getLine
имеет тип getLine :: IO String
: каждый раз, когда оценивается действие, создается String
(путем чтения от пользователя).Аналогично, putStr
имеет тип putStr :: String -> IO ()
;это функция, которая принимает строку и возвращает действие, которое при запуске не возвращает никакой полезной информации ... но, как побочный эффект, печатает что-то на экране.
Вы пытаетесь написать функцию типаIO () -> ([Char], Int)
.Это будет функция, которая принимает в качестве входных данных действие и возвращает кортеж, который не , что вы хотите.Вы хотите IO (String, Int)
- действие, которое при запуске создает кортеж, состоящий из строки (которая является синонимом [Char]
) и целого числа.Ты тоже почти со своим текущим кодом!Это то, что вам нужно вместо этого:
investinput :: IO (String, Int)
investinput = do
putStrLn "Enter Username : "
username <- getLine
putStrLn "Enter Invest Amount : "
tempamount <- getLine
let amount = read tempamount
return (username, amount)
Обратите внимание, что я только сделал два изменения (и удалил пустую строку).Во-первых, я изменил тип функции, как я сказал выше.Во-вторых, я изменил show
на read
.Функция show
имеет тип Show a => a -> String
: это функция, которая принимает все, что может быть показано, и создает строку, представляющую ее. Вы хотели read
, который имеет тип Read a => String -> a
: по заданной строке он анализирует ее и возвращает некоторое читаемое значение.
Другая вещь, о которой вы спрашивали, - это возврат кортежа (String, Int)
вместо действия IO (String, Int)
.Нет чистого способа сделать это;другими словами, нет чистой функции IO a -> a
.Почему это?Потому что IO a
представляет собой нечистое действие, которое зависит от реального мира.Если бы у нас была такая функция impossibleRunIO :: IO a -> a
, то мы бы хотели, чтобы это было в случае impossibleRunIO getLine == impossibleRunIO getLine
, поскольку функция должна быть чистой.Но это бесполезно, поскольку нам бы хотелось, чтобы impossibleRunIO
действительно мог взаимодействовать с реальным миром!Таким образом, эта чистая функция невозможна.Все, что входит в IO
, никогда не может уйти.Вот что делает return
: это функция, в данном случае 1 , с типом return :: a -> IO a
, которая позволяет вам помещать чистые значения в IO
.Для любого x
, return x
- это действие, которое при запуске всегда выдает x
.Вот почему вы должны завершить свой блок do
с помощью return
: username
- это чистое значение, которое вы извлекли из действия, и поэтому оно отображается только внутри блока do
.Вам нужно поднять его в IO
, прежде чем внешний мир сможет это увидеть.То же самое относится к amount
/ tempamount
.
И просто для полноты картины: за этим стоит некая всеобъемлющая теория, которая связывает ее вместе.Но это вовсе не обязательно для начала программирования на Haskell.Что я бы порекомендовал сделать, так это структурировать большую часть вашего кода как чистые функции, которые сворачивают, разворачивают и деформируют ваши данные.Затем создайте тонкий (как можно более тонкий) IO
передний слой, который взаимодействует с указанными функциями.Вы будете удивлены, как мало IO вам нужно!
1: На самом деле он имеет более общий тип, но на данный момент это не актуально.