#haskell #io
#haskell #io
Вопрос:
Я изучаю Haskell, и в качестве упражнения я написал программу, которая считывает целое число и печатает следующее:
main = do
line_with_n_in_it <- getLine
putStrLn $ show $ (read line_with_n_in_it :: Int) 1
Однако мне кажется довольно глупым, что я должен явно называть строку, которую я читаю.
Если я напишу
main = do
putStrLn $ show $ (read getLine :: Int) 1
runghc
жалуется, что read
ожидает String
, но getLine
предоставляет IO String
. Итак, из того, что я понимаю, кажется, что этот волшебный <-
оператор преобразуется IO String
в String
.
Есть ли другой оператор или функция, которые позволят мне просто встроить <-
оператор? Так что, если бы magic
была моей волшебной функцией, я смог бы написать свою новую программу как
main = do
putStrLn $ show $ (read $ magic getLine :: Int) 1
Комментарии:
1.
fmap ( 1) readLn >>= print
2. Или,
import Control.Applicative
чтобы получить магию$
, иначе<$>
, дающую( 1) <$> getLine >>= print
Ответ №1:
Вам следует прочитать о монадах (и IO monad)
Проблема в том, что вы должны «извлечь» значение из вашей монады, и это не «точно» вызов функции.
Ваш первый код правильный, вы извлекаете некоторое значение из monad
readedString <- getLine
а затем используйте это
putStrLn $ "Readed: " readedString
вы можете избежать «именования строки», но, в общем, вполне нормально писать имена для этого.
чтобы избежать именования, вы должны написать некоторую монадическую функцию, а затем привязать
getLine >>= putStrLn . show . ( 1) . read
но, опять же, я рекомендую вам прочитать о монадах (и IO monad).
Кстати, <-
оператор «равен» >>=
operator, подробности здесь.
Комментарии:
1. Это может быть немного не по теме — но почему вам не нужно комментировать тип
read
? Поскольку это выглядит так, что( 1)
может быть применено как кFloat
, так и кInt
, так как же haskell узнает, что выбрать?2. @math4tots: Раздел 4.3.4 Неоднозначные типы и значения по умолчанию для перегруженных числовых операций : «Каждая переменная по умолчанию заменяется первым типом в списке по умолчанию, который является экземпляром всех классов неоднозначной переменной. . Значения по умолчанию —
(Integer,Double)
, и посколькуInteger
это достаточно хорошо для этих операций, оно используется.3. Здесь нет простого ответа 🙂 Haskell нужно знать конечный тип только при его использовании. В моей последней строке Haskell использует (по соглашению)
Integer
type (при записи появляется ошибка3.14
). Но «не окончательное» функциональное приложение является универсальным. Посмотрите на тип( 1) . read :: (Num c, Read c) => String -> c
.
Ответ №2:
Тот факт, что Haskell явно запрещает такое поведение, как раз и делает его таким ценным — вы не можете смешивать чистый код с кодом, пронизанным IO.
Волшебный <-
оператор — это синтаксический сахар для монадической привязки. Например, ваша исходная функция может быть переписана явно с помощью >>=
оператора (произносится «bind»):
main = do
n <- getLine
putStrLn $ show $ (read n :: Int) 1
===
main = getLine >>= n -> putStrLn $ show $ (read n :: Int) 1
Тот факт, что <-
вам это кажется волшебством, это хорошо — это не стандартный синтаксис haskell, это сахар. Последний способ написания функции должен иметь больше смысла.
Что касается того, почему это работает, я могу дать вам подробное объяснение, специфичное для монады ввода-вывода, и предложить вам прочитать главы о монадах Learn You a Haskell for Great Good! (или всю книгу, если вы действительно новичок!), чтобы углубить ваше понимание.
Поехали. getLine
считывает String
из IO
, верно? Но это означает, что результат вызова «помечен IO»; это String
завернутый в IO
контекст, которым мы не можем манипулировать, как мы могли бы обычным String
образом. Выполнение такой вещи нарушило бы ссылочную прозрачность, поскольку IO
контекст (то есть то, что вы читаете) может измениться. По сути, что делает этот волшебный <-
оператор, так это «вытаскивает» чистое String
значение из IO
контекста и позволяет вам оперировать с ним. Более явно, >>=
оператор выполняет IO
действие ( getLine
) и функцию, которая преобразует String
в новое IO
действие (лямбда-выражение в переведенном примере) и возвращает это новое IO
действие. Это способ объединения IO
выражений воедино и «копания» в их внутренних значениях, оперирования с ними и повторного преобразования их в IO
. Монады в целом — это не то, что я (или кто-либо другой, если уж на то пошло) могу объяснить вам во всех подробностях в ответе SO, но я настоятельно рекомендую вам прочитать LYAH, если вы намерены продолжать изучать Haskell — это отличная книга, которая быстро введет вас в курс дела.