Входной возвращаемый кортеж на Haskell - PullRequest
4 голосов
/ 18 июня 2010

Интересно, может ли функция IO () вернуть кортеж, потому что я хотел бы получить их из этой функции в качестве входных данных для другой функции.

investinput :: IO()->([Char], Int)
investinput = do
 putStrLn "Enter Username : "
 username <- getLine

 putStrLn "Enter Invest Amount : "
 tempamount <- getLine
 let amount = show  tempamount
 return (username, amount)

Пожалуйста, помогите.

Спасибо.

Ответы [ 4 ]

8 голосов
/ 18 июня 2010

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: На самом деле он имеет более общий тип, но на данный момент это не актуально.

4 голосов
/ 18 июня 2010

Да, вы почти у цели, но я думаю, что вы хотите подпись:

investinput :: IO ([Char], Int)

... тогда из вызывающей функции вы можете сделать что-то вроде:

main = do
    (username, amount) <- investinput
    ....

Я думаю, что вы хотите читать tempamount, а не показывать.

2 голосов
/ 18 июня 2010

Функция ввода-вывода, которая создает кортеж, будет иметь тип IO (a, b), в этом случае:

investinput :: IO ([Char], Int)

Подпись IO () -> ([Char], Int) будет означать, что функция принимает параметр типа IO () и создает из него кортеж, а это не то, что вам нужно.

Как правило, нет ограничений на типы, которые функция IO (или функция в другой монаде) может возвращать, вы можете выбирать типы по своему усмотрению.

1 голос
/ 18 июня 2010

Ответ на ваш вопрос о возвращении (String, Int) вместо IO (String, Int) прост: вы не можете.Как только вы попали в IO, вы застряли там.Это часть того, что означает, когда люди говорят, что Haskell - «чистый» язык.

То, что вы хотите сделать, похоже на то, что вы уже делаете здесь с getLine.Тип getLine равен IO String.Когда вы пишете username <- getLine, вы фактически извлекаете String из IO String, но это возможно только потому, что вы находитесь внутри выражения do.

Вы можете сделать точното же самое с investinput, как с getLine.Вот пример того, как вы можете использовать investinput в вашей функции main:

main = do
  (name, amount) <- investinput
  putStrLn $ name ++ " invested $" ++ show amount ++ "."

Поскольку вы упоминаете liftM в комментарии, вот полная рабочая версия, которая делает то же самое, используя liftM и оператор обратного связывания (=<<) вместо do нотации:

import Control.Monad (liftM)

investinput :: IO (String, Int)
investinput = do
   putStrLn "Enter Username : "
   username <- getLine
   putStrLn "Enter Invest Amount : "
   amount <- liftM read getLine -- We can also use liftM to get rid of tempamount.
   return (username, amount)

summary :: (String, Int) -> String
summary (name, amount) = name ++ " invested $" ++ show amount ++ "."

main = putStrLn =<< liftM summary investinput

Здесь показано, как можно использовать investinput с «другой функцией, которая ожидает кортеж».

...