GHC.IO
| Авторские права | (c) The University of Glasgow 1994-2023 |
|---|---|
| Лицензия | см. libraries/base/LICENSE |
| Сопровождающие | ghc-devs@haskell.org |
| Стабильность | внутренний |
| Переносимость | непереносимый (расширения GHC) |
| Безопасный Haskell | отсутствует |
| Язык | Haskell2010 |
Описание
Определения для монады IO и связанных с ней сущностей.
API этого модуля нестабилен и не предназначен для использования широкой публикой. Если вам совершенно необходимо от него зависеть, обязательно задайте строгую верхнюю границу версии, например base < 4.X вместо base < 5, поскольку интерфейс может быстро измениться без особого предупреждения.
Значение типа IO a — это вычисление, которое при выполнении производит некоторые операции ввода-вывода, а затем возвращает значение типа a.
На самом деле существует лишь один способ «выполнить» действие ввода-вывода: связать его с Main.main в вашей программе. При запуске программы ввод-вывод будет выполнен. Невозможно выполнять ввод-вывод из произвольной функции, если только сама эта функция не находится в монаде IO и не вызывается в какой-то момент, прямо или косвенно, из Main.main.
IO — это монада, поэтому действия IO можно объединять, используя do-нотацию или операции >> и >>= из класса Monad.
Экземпляры
unIO :: IO a -> State# RealWorld -> (# State# RealWorld, a #) Источник
liftIO :: IO a -> State# RealWorld -> STret RealWorld a Источник
mplusIO :: IO a -> IO a -> IO a Источник
unsafePerformIO :: IO a -> a Источник
Это «чёрный ход» в монаду IO, позволяющий выполнять вычисления IO в любой момент. Чтобы это было безопасно, вычисление IO должно быть свободно от побочных эффектов и не зависеть от окружения.
Если вычисление ввода-вывода, обёрнутое в unsafePerformIO, выполняет побочные эффекты, то относительный порядок их выполнения (по отношению к основному потоку ввода-вывода или другим вызовам unsafePerformIO) не определён. Кроме того, при использовании unsafePerformIO для создания побочных эффектов следует принять следующие меры предосторожности, чтобы обеспечить их выполнение ожидаемое число раз. Обратите внимание: для GHC эти меры необходимы, но могут быть недостаточны; другим компиляторам могут потребоваться иные меры:
- Используйте
{-# NOINLINE foo #-}как прагму для любой функцииfoo, вызывающейunsafePerformIO. Если вызов будет встроен, ввод-вывод может быть выполнен более одного раза. - Используйте флаг компилятора
-fno-cse, чтобы запретить устранение общих подвыражений в модуле: оно может объединить два побочных эффекта, которые должны были оставаться раздельными. Хороший пример — использование нескольких глобальных переменных (например,testв примере ниже). - Убедитесь, что либо вы отключили перемещение let-выражений (
-fno-full-laziness), либо вызовunsafePerformIOне может быть перемещён за пределы лямбда-выражения. Например, если написать:f x = unsafePerformIO (newIORef []), можно получить только одну ячейку ссылки, общую для всех вызововf. Лучше написатьf x = unsafePerformIO (newIORef [x]), поскольку теперь выражение не может быть перемещено за пределы лямбда-выражения.
Мало кто знает, что unsafePerformIO не является типобезопасной. Например:
test :: IORef [a]
test = unsafePerformIO $ newIORef []
main = do
writeIORef test [42]
bang <- readIORef test
print (bang :: [Char])
Эта программа завершится с дампом ядра. Проблема полиморфных ссылок хорошо известна в сообществе ML и не возникает при обычном монадиальном использовании ссылок. После использования unsafePerformIO нет простого способа сделать такую ситуацию невозможной. Более того, с помощью unsafePerformIO можно написать coerce :: a -> b. Будьте осторожны!
ПРЕДУПРЕЖДЕНИЕ: если вы ищете «способ получить String из 'IO String'», то unsafePerformIO — не то, что вам нужно. Прежде чем продолжить, изучите do-нотацию и синтаксическую конструкцию <-.
unsafeInterleaveIO :: IO a -> IO a Источник
unsafeInterleaveIO позволяет отложить вычисление IO, используя ленивость. Если передать значение типа IO a, вычисление IO будет выполнено только при востребовании значения a. Это используется для реализации ленивого чтения файлов; см. hGetContents.
unsafeDupablePerformIO :: IO a -> a Источник
Эта версия unsafePerformIO эффективнее, поскольку не проверяет, что ввод-вывод выполняется только одним потоком. Поэтому при использовании unsafeDupablePerformIO действие ввода-вывода может быть выполнено несколько раз (на многопроцессорной системе), и следует убедиться, что каждый раз оно даёт одинаковые результаты. Может случиться даже так, что одно из дублированных действий ввода-вывода будет выполнено лишь частично, а затем прервано без возникновения исключения. Поэтому такие функции, как bracket, нельзя безопасно использовать внутри unsafeDupablePerformIO.
Начиная с: base-4.4.0.0
unsafeDupableInterleaveIO :: IO a -> IO a Источник
unsafeDupableInterleaveIO позволяет отложить вычисление IO, используя ленивость. Если передать значение типа IO a, вычисление IO будет выполнено только при востребовании значения a.
Вычисление может быть выполнено несколько раз разными потоками, возможно, одновременно. Чтобы гарантировать, что оно будет выполнено только один раз, используйте unsafeInterleaveIO.
noDuplicate :: IO () Источник
Гарантирует уникальность приостановленных вычислений, выполняемых текущим потоком; то есть текущий поток не вычисляет ничего, что одновременно вычисляется другим потоком, также выполнившим noDuplicate.
Эта операция используется в определении unsafePerformIO, чтобы предотвратить многократное выполнение действия ввода-вывода, что обычно нежелательно.
stToIO :: ST RealWorld a -> IO a Источник
Встраивает поток строгих состояний в действие IO. Параметр RealWorld указывает, что внутреннее состояние, используемое вычислением ST, является специальным и предоставляется монадой IO, а значит, отличается от состояний, используемых вызовами runST.
ioToST :: IO a -> ST RealWorld a Источник
Преобразует действие IO в действие ST. Тип результата ограничен использованием потока состояний RealWorld, поэтому результат нельзя передать в runST.
unsafeIOToST :: IO a -> ST s a Источник
Преобразует действие IO в действие ST. Это основано на том, что IO и ST имеют одинаковое представление, если не учитывать ограничение на параметр типа потока состояний.
unsafeSTToIO :: ST s a -> IO a Источник
Преобразует действие ST в действие IO. Это основано на том, что IO и ST имеют одинаковое представление, если не учитывать ограничение на параметр типа потока состояний.
Пример, демонстрирующий, почему это небезопасно: https://mail.haskell.org/pipermail/haskell-cafe/2009-April/060719.html
type FilePath = String Источник
Имена файлов и каталогов — это значения типа String, точное значение которого зависит от операционной системы. Файлы можно открывать, получая дескриптор, который затем используется для работы с содержимым файла.
Аргументы
| :: Exception e | |
| => IO a | Вычисление, которое нужно выполнить |
| -> (e -> IO a) | Обработчик, вызываемый при возникновении исключения |
| -> IO a |
Это самая простая функция перехвата исключений. Она принимает один аргумент, выполняет его и, если возникает исключение, запускает «обработчик», передавая ему значение исключения в качестве аргумента. В противном случае результат возвращается обычным образом. Например:
catch (readFile f)
(\e -> do let err = show (e :: IOException)
hPutStr stderr ("Warning: Couldn't open " ++ f ++ ": " ++ err)
return "")
Обратите внимание: необходимо указать сигнатуру типа для e, иначе программа не пройдёт проверку типов из-за неоднозначности типа. Хотя можно перехватывать исключения любого типа, объяснение проблем, связанных с этим, см. в разделе «Перехват всех исключений» (в Control.Exception).
О перехвате исключений в чистых выражениях (не-IO) см. функцию evaluate.
Обратите внимание: из-за неуказанного порядка вычисления в Haskell выражение может вызвать одно из нескольких возможных исключений: рассмотрим выражение (error "urk") + (1 `div` 0). Вызывает ли это выражение ErrorCall "urk" или DivideByZero?
Ответ: «оно может вызвать любое из них»; выбор недетерминирован. Если вы перехватываете исключения любого типа, можно перехватить любое из них. Если вызвать catch с типом IO Int -> (ArithException -> IO Int) -> IO Int, обработчик может быть запущен с аргументом DivideByZero или же исключение ErrorCall "urk" может быть передано дальше вверх по стеку вызовов. При повторном вызове может произойти обратное. Это допустимо, поскольку catch — вычисление IO.
catchException :: Exception e => IO a -> (e -> IO a) -> IO a Источник
Перехватывает исключение в монаде IO.
Обратите внимание: эта функция является строгой относительно действия. То есть catchException undefined b == _|_. Подробности см. в .
catchAny :: IO a -> (forall e. (HasExceptionContext, Exception e) => e -> IO a) -> IO a Источник
Перехватывает исключение любого типа Exception в монаде IO.
Обратите внимание: эта функция является строгой относительно действия. То есть catchAny undefined b == _|_. Подробности см. в .
Если вы повторно выбрасываете исключение, следует повторно использовать переданный ExceptionContext.
throwIO :: (HasCallStack, Exception e) => e -> IO a Источник
Вариант throw, который можно использовать только внутри монады IO.
Хотя throwIO имеет тип, являющийся экземпляром типа throw, эти две функции немного различаются:
throw e `seq` () ===> throw e throwIO e `seq` () ===> ()
В первом примере будет вызвано исключение e, а во втором — нет. Фактически, throwIO вызывает исключение только при использовании внутри монады IO.
Для вызова исключения внутри монады IO следует предпочесть вариант throwIO вместо throw, поскольку он гарантирует порядок выполнения относительно других операций, а throw такой гарантии не даёт. Говорят, что throwIO выбрасывает *точные* исключения, а throw, error и т. д. выбрасывают *неточные* исключения. Например:
throw e + error "boom" ===> error "boom" throw e + error "boom" ===> throw e
оба являются допустимыми шагами редукции, и компилятор может выбрать любой из них (даже бесконечный цикл), тогда как
throwIO e >> error "boom" ===> throwIO e
при выполнении всегда вызовет e.
Более подробное техническое введение в то, как GHC оптимизирует код с учётом точных и неточных исключений, см. также на вики-странице GHC о точных исключениях.
mask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Источник
Выполняет вычисление ввода-вывода с замаскированными асинхронными исключениями. Это означает, что любой поток, пытающийся вызвать исключение в текущем потоке с помощью throwTo, будет заблокирован, пока маскирование асинхронных исключений не будет снято.
Аргумент, передаваемый в mask, — это функция, принимающая другую функцию. Её можно использовать для восстановления действующего состояния маскирования в контексте замаскированного вычисления. Например, часто mask используют для защиты получения ресурса:
mask $ \restore -> do
x <- acquire
restore (do_something_with x) `onException` release
release
Этот код гарантирует, что acquire будет сопряжён с release, маскируя асинхронные исключения на критически важных участках. (Вместо того чтобы писать этот код самостоятельно, лучше использовать bracket, которая абстрагирует общий шаблон.)
Обратите внимание: действие restore, переданное аргументу mask, не обязательно снимает маскирование асинхронных исключений — оно лишь восстанавливает состояние маскирования, действовавшее во внешнем контексте. Поэтому, если асинхронные исключения уже замаскированы, mask нельзя использовать, чтобы снова снять маскирование. Это позволяет быть уверенным, что библиотечный вызов, которому вы передали замаскированные исключения, не сможет снять маскирование. Если вы пишете библиотечный код и вам нужны асинхронные исключения, единственный способ — создать новый поток; см. forkIOWithUnmask.
Асинхронные исключения всё ещё могут поступать в замаскированном состоянии, если замаскированный поток блокируется определённым образом; см. Control.Exception.
Потоки, созданные с помощью forkIO, наследуют MaskingState родительского потока; то есть, чтобы запустить поток в состоянии MaskedInterruptible, используйте mask_ $ forkIO .... Это особенно полезно, если необходимо установить обработчик исключений в порождённом потоке до получения каких-либо асинхронных исключений. Чтобы создать новый поток в состоянии без маскирования, используйте forkIOWithUnmask.
mask_ :: IO a -> IO a Источник
Аналог mask, но аргументу не передаётся действие restore.
uninterruptibleMask :: ((forall a. IO a -> IO a) -> IO b) -> IO b Источник
Аналог mask, но замаскированное вычисление нельзя прервать (см. Control.Exception). ЭТУ ФУНКЦИЮ СЛЕДУЕТ ИСПОЛЬЗОВАТЬ С БОЛЬШОЙ ОСТОРОЖНОСТЬЮ: если поток, выполняющийся в uninterruptibleMask, по какой-либо причине заблокируется, он (а возможно, и программа, если это главный поток) перестанет отвечать и его нельзя будет завершить. Эта функция нужна только в том случае, если необходимо маскировать исключения во время прерываемой операции и можно гарантировать, что такая операция будет блокироваться лишь непродолжительное время.
uninterruptibleMask_ :: IO a -> IO a Источник
Аналог uninterruptibleMask, но аргументу не передаётся действие restore.
data MaskingState Источник
Описывает поведение потока при получении асинхронного исключения.
Конструкторы
| Unmasked | асинхронные исключения не замаскированы (обычное состояние) |
| MaskedInterruptible | состояние во время |
| MaskedUninterruptible | состояние во время |
Экземпляры
| Eq MaskingState Источник | Начиная с: base-4.3.0.0 |
Определено в GHC.Internal.IO Методы(==) :: MaskingState -> MaskingState -> Bool Источник (/=) :: MaskingState -> MaskingState -> Bool Источник | |
| Show MaskingState Источник | Начиная с: base-4.3.0.0 |
Определено в GHC.Internal.IO МетодыshowsPrec :: Int -> MaskingState -> ShowS Источник show :: MaskingState -> String Источник showList :: [MaskingState] -> ShowS Источник | |
getMaskingState :: IO MaskingState Источник
Возвращает MaskingState текущего потока.
unsafeUnmask :: IO a -> IO a Источник
interruptible :: IO a -> IO a Источник
Разрешает возникновение асинхронных исключений даже внутри mask, делая операцию прерываемой (см. обсуждение «Прерываемых операций» в Exception).
При вызове вне mask или внутри uninterruptibleMask эта функция не оказывает эффекта.
Начиная с: base-4.9.0.0
onException :: IO a -> IO b -> IO a Источник
Аргументы
| :: IO a | вычисление, которое нужно выполнить первым («получение ресурса») |
| -> (a -> IO b) | вычисление, которое нужно выполнить последним («освобождение ресурса») |
| -> (a -> IO c) | вычисление, которое нужно выполнить между ними |
| -> IO c |
Аргументы
| :: IO a | вычисление, которое нужно выполнить первым |
| -> IO b | вычисление, которое нужно выполнить после (даже если было вызвано исключение) |
| -> IO a |
evaluate :: a -> IO a Источник
Вычисляет аргумент до слабой головной нормальной формы.
evaluate обычно используется для выявления исключений, которые может содержать ленивое значение, и, возможно, их обработки.
evaluate вычисляет значение только до слабой головной нормальной формы. Если требуется более глубокое вычисление, может пригодиться функция force из Control.DeepSeq:
evaluate $ force x
Между evaluate x и return $! x есть тонкое различие, аналогичное различию между throwIO и throw. Если ленивое значение x вызывает исключение, return $! x не сможет вернуть действие IO и вместо этого вызовет исключение. evaluate x, напротив, всегда создаёт действие IO; это действие вызовет исключение при выполнении тогда и только тогда, когда x вызывает исключение при вычислении.
Практическое следствие этого различия заключается в том, что из-за семантики неточных исключений
(return $! error "foo") >> error "bar"
может вызвать либо "foo", либо "bar" в зависимости от оптимизаций, выполняемых компилятором. С другой стороны,
evaluate (error "foo") >> error "bar"
гарантированно вызовет "foo".
Общее правило: используйте evaluate, чтобы принудительно вычислить ленивые значения или обработать возникающие в них исключения. Если же вы вычисляете ленивое значение только ради повышения эффективности и исключения вас не беспокоят, можно использовать return $! x.
mkUserError :: [Char] -> SomeException Источник
© The University of Glasgow and others
Licensed under a BSD-style license (see top of the page).
https://downloads.haskell.org/~ghc/9.14.1/docs/libraries/base-4.22.0.0-66f8/GHC-IO.html