Основы функционального программирования/Haskell/Ввод-вывод: различия между версиями
мНет описания правки |
м Викификация, ссылки, пунктуация, прочее |
||
Строка 1: | Строка 1: | ||
__TOC__ |
__TOC__ |
||
{{ОФП Содержание}} |
{{ОФП Содержание}} |
||
В |
В [[w:Haskell|Haskell]], как и во всех остальных [[w:Язык программирования|языках]], существует всесторонняя система операций ввода-вывода. Хотя обычно операции ввода-вывода предполагают некоторую последовательность в своём выполнении, то есть по сути дела [[w:Императивное программирование|императивность]], в Haskell система операций ввода-вывода полностью поддерживает [[w:Функциональное программирование|функциональную парадигму программирования]]. |
||
Уже говорилось, что все операции ввода |
Уже говорилось, что все операции ввода-вывода построены при помощи такого понятия языка Haskell, как [[w:Монада (математика)|монада]] ([[#Основы функционального программирования/Модули и монады в Haskell|лекция 6]]). В то же время для понимания системы операций ввода-вывода в Haskell нет особой необходимости понимать теоретические основы понятия монада. Монады можно рассматривать как концептуальные рамки, в которых содержится система ввода-вывода. Можно сказать, что понимание [[w:Теория категорий|теории категорий]] так же необходимо для использования системы операций ввода-вывода в Haskell, как и понимание [[w:Теория групп|теории групп]] для выполнения арифметических операций. |
||
Операции ввода |
Операции ввода-вывода в любом языке основаны на понятии действия. При возбуждении действия оно выполняется. Однако в Haskell действия не возбуждаются, а скорее просто декларируются. В свою очередь действия могут быть атомарными или составленными из последовательности других действий. Монада <code>IO</code> содержит операции, которые позволяют создавать сложные действия из атомарных. То есть монаду в данном случае можно рассматривать как клей, который связывает действия в [[w:Компьютерная программа|программе]]. |
||
== Базовые операции ввода |
== Базовые операции ввода-вывода == |
||
Каждое действие ввода |
Каждое действие ввода-вывода возвращает какое-то значение. Для того, чтобы различать эти значения от базовых, [[w:Тип данных|типы]] этих значений как бы обёрнуты типом <code>IO</code> (необходимо помнить, что монада является [[w:Контейнер (программирование)|контейнерным типом]]). Например, тип [[w:Функция (программирование)|функции]] <code>getChar</code> следующий: |
||
getChar |
<code>getChar :: IO Char</code> |
||
В этом примере показано, что функция getChar выполняет некоторое действие, которое возвращает значение типа Char. Действия, которые не возвращают ничего интересного, имеют тип IO (). |
В этом примере показано, что функция <code>getChar</code> выполняет некоторое действие, которое возвращает значение типа <code>Char</code>. Действия, которые не возвращают ничего интересного, имеют тип <code>IO ()</code>. То есть символ <code>()</code> обозначает пустой тип (<code>void</code> в других языках). Так функция <code>putChar</code> имеет тип: |
||
putChar |
<code>putChar :: Char -> IO ()</code> |
||
Друг с другом действия связываются при помощи оператора связывания. |
Друг с другом действия связываются при помощи оператора связывания. То есть символы <code>>>=</code> выстраивают последовательность действий. Как известно, вместо этой функции можно использовать служебное слово <code>do</code>. Оно использует такой же двумерный [[w:Синтаксис (программирование)|синтаксис]], как и слова <code>let</code> и <code>where</code>, поэтому можно не использовать для разделения вызова функций символ <code>;</code>. При помощи слова <code>do</code> можно связывать вызовы функций, определение данных (при помощи символов <code>-></code>) и множество определений локальных [[w:Переменная (программирование)|переменных]] (служебное слово <code>let</code>). |
||
Например, так можно определить программу, которая читает символ с клавиатуры и выводит его на экран: |
Например, так можно определить программу, которая читает символ с [[w:Клавиатура|клавиатуры]] и выводит его на [[w:Монитор|экран]]: |
||
main :: IO () |
<code>main :: IO () |
||
main = do c |
main = do c <- getChar |
||
putChar c</code> |
|||
В этом примере не случайно для имени функции выбрано слово main. В |
В этом примере не случайно для имени функции выбрано слово <code>main</code>. В Haskell, также, как и в языках [[w:Си (язык программирования)|Си]]/[[w:C++|Си++]] название функции <code>main</code> используется для обозначения точки входа в программу. Кроме того, в Haskell тип функции <code>main</code> должен быть типом монады <code>IO</code> (обычно используется <code>IO ()</code>). Ко всему прочему, точка входа в виде функции <code>main</code> должна быть определена в модуле с именем <code>Main</code>. |
||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
⚫ | |||
c == ’y’ |
|||
⚫ | |||
c == 'y'</code> |
|||
⚫ | Потому что в этом случае результатом выполнения операции сравнения будет значение типа <code>Bool</code>, а не <code>IO Bool</code>. Для того, чтобы возвращать монадические значения, существует специальная функция <code>return</code>, которая из простого типа данных делает монадический. То есть в предыдущем примере последняя строка определения функции <code>ready</code> должна была выглядеть как <code>return (c == 'y')</code>. |
||
⚫ | Потому что в этом случае результатом выполнения операции сравнения будет значение типа Bool, а не IO Bool. Для того, чтобы возвращать монадические значения, существует специальная функция return, которая из простого типа данных делает монадический. |
||
В следующем примере показана более сложная функция, которая считывает строку символов с клавиатуры: |
В следующем примере показана более сложная функция, которая считывает строку символов с клавиатуры: |
||
'''Пример 16. Функция getLine.''' |
'''Пример 16. Функция <code>getLine</code>.''' |
||
getLine :: IO String |
<code>getLine :: IO String |
||
getLine = do c |
getLine = do c <- getChar |
||
if c == '\n' then return "" |
|||
else do l <- getLine |
|||
return (c : l)</code> |
|||
Необходимо помнить, что в тот момент, когда программист перешёл в мир действий (использовал систему операций ввода |
Необходимо помнить, что в тот момент, когда [[w:Программист|программист]] перешёл в мир действий (использовал систему операций ввода-вывода), назад пути нет. То есть если функция не использует монадический тип <code>IO</code>, то она не может заниматься вводом/выводом, и наоборот, если функция возвращает монадический тип <code>IO</code>, то она должна подчиняться парадигме действий в Haskell. |
||
== Программирование при помощи действий == |
== Программирование при помощи действий == |
||
Действия ввода |
Действия ввода-вывода являются обычными значениями в терминах Haskell. То есть действия можно передавать в функции в качестве [[w:Параметр (программирование)|параметров]], заключать в [[w:Структура данных|структуры данных]] и вообще использовать там, где можно использовать данные языка Haskell. В этом смысле система операций ввода-вывода является полностью функциональной. Например, можно предположить список действий: |
||
todoList :: [IO ()] |
<code>todoList :: [IO ()] |
||
todoList = [putChar |
todoList = [putChar 'a', |
||
do putChar 'b' |
|||
putChar 'c', |
|||
do c <- getChar |
|||
putChar 'c']</code> |
|||
Этот список не возбуждает никаких действий, он просто содержит их описания. Для того, чтобы выполнить эту структуру, |
Этот список не возбуждает никаких действий, он просто содержит их описания. Для того, чтобы выполнить эту структуру, то есть возбудить все её действия, необходима некоторая функция (например, <code>sequence_</code>): |
||
sequence_ :: [IO ()] - |
<code>sequence_ :: [IO ()] -> IO () |
||
sequence_ [] = return () |
sequence_ [] = return () |
||
sequence_ (a:as) = do a |
sequence_ (a:as) = do a |
||
sequence as</code> |
|||
Эта функция может быть полезна для написания функции putStr, которая выводит строку на экран: |
Эта функция может быть полезна для написания функции <code>putStr</code>, которая выводит строку на экран: |
||
putStr |
<code>putStr :: String -> IO () |
||
putStr s = sequence_ (map putChar s) |
putStr s = sequence_ (map putChar s)</code> |
||
На этом примере видно явное отличие системы операций ввода |
На этом примере видно явное отличие системы операций ввода-вывода языка Haskell от систем императивных языков. Если бы в каком-нибудь императивном языке была бы функция <code>map</code>, она бы выполнила кучу действий. Вместо этого в Haskell просто создаётся список действий (одно для каждого символа строки), который потом обрабатывается функцией <code>sequence_</code> для выполнения. |
||
== Обработка исключений == |
== [[w:Обработка исключений|Обработка исключений]] == |
||
Что делать, если в процессе операций ввода |
Что делать, если в процессе операций ввода-вывода возникла неординарная ситуация? Например, функция <code>getChar</code> обнаружила конец [[w:Файл|файла]]. В этом случае произойдёт ошибка. Как и любой продвинутый язык программирования, Haskell предлагает для этих целей механизм обработки исключений. Для этого не используется какой-то специальный синтаксис, но есть специальный тип <code>IOError</code>, который содержит описания всех возникаемых в процессе ввода-вывода ошибок. |
||
Обработчик исключений имеет тип (IOError - |
Обработчик исключений имеет тип <code>(IOError -> IO a)</code>, при этом функция <code>catch</code> ассоциирует (связывает) обработчик исключений с набором действий: |
||
catch :: IO a - |
<code>catch :: IO a -> (IOError -> IO a) -> IO a</code> |
||
Аргументами этой функции являются действие (первый аргумент) и обработчик исключений (второй аргумент). Если действие выполнено успешно, то просто возвращается результат без возбуждения обработчика исключений. Если же в процессе выполнения действия возникла ошибка, то она |
[[w:Аргумент (программирование)|Аргументами]] этой функции являются действие (первый аргумент) и обработчик исключений (второй аргумент). Если действие выполнено успешно, то просто возвращается результат без возбуждения обработчика исключений. Если же в процессе выполнения действия возникла ошибка, то она передаётся обработчику исключений в качестве операнда типа <code>IOError</code>, после чего выполняется сам обработчик. |
||
Таким образом, можно написать более сложные функции, которые будут грамотно вести себя в случае |
Таким образом, можно написать более сложные функции, которые будут грамотно вести себя в случае возникновения ошибочных ситуаций: |
||
<code>getChar' :: IO Char |
|||
getChar' = getChar `catch` eofHandler |
|||
where eofHandler e = if isEofError e then return \ |
where eofHandler e = if isEofError e then return \'n\ else ioError e |
||
getLine' :: IO String |
|||
getLine' = catch getLine'<em/>' (\err -> return ("Error: " ++ show err)) |
|||
where |
where getLine'<em/>' = do c <- getChar' |
||
if c == '\n' then return "" |
|||
else do l <- getLine' |
|||
return (c : l)</code> |
|||
В этой программе видно, что можно использовать вложенные друг в друга обработчики ошибок. В функции |
В этой программе видно, что можно использовать вложенные друг в друга обработчики ошибок. В функции <code>getChar'</code> отлавливается ошибка, которая возникает при обнаружении символа конца файла. Если ошибка другая, то при помощи функции <code>ioError</code> она отправляется дальше и ловится обработчиком, который «сидит» в функции <code>getLine'</code>. Для определённости в Haskell предусмотрен обработчик исключений по умолчанию, который находится на самом верхнем уровне вложенности. Если ошибка не поймана ни одним обработчиком, который написан в программе, то её ловит обработчик по умолчанию, который выводит на экран сообщение об ошибке и останавливает программу. |
||
== Файлы, каналы и обработчики == |
== Файлы, каналы и обработчики == |
||
Для работы с файлами Haskell предоставляет все возможности, что и другие языки программирования. Однако большинство этих возможностей определены в модуле IO, а не в Prelude, поэтому для работы с файлами необходимо явно импортировать модуль IO. |
Для работы с файлами Haskell предоставляет все возможности, что и другие языки программирования. Однако большинство этих возможностей определены в модуле <code>IO</code>, а не в <code>Prelude</code>, поэтому для работы с файлами необходимо явно импортировать модуль <code>IO</code>. |
||
Открытие файла порождает обработчик (он имеет тип Handle). Закрытие обработчика инициирует закрытие соответствующего файла. Обработчики могут быть также ассоциированы с каналами, |
Открытие файла порождает обработчик (он имеет тип <code>Handle</code>). Закрытие обработчика инициирует закрытие соответствующего файла. Обработчики могут быть также ассоциированы с каналами, то есть портами взаимодействия, которые не связаны напрямую с файлами. В Haskell предопределены [[w:Стандартные потоки|три таких канала]] — <code>stdin</code> (стандартный канал ввода), <code>stdout</code> (стандартный канал вывода) и <code>stderr</code> (стандартный канал вывода сообщений об ошибках). |
||
Таким образом, для использования файлов можно пользоваться следующими вещами: |
Таким образом, для использования файлов можно пользоваться следующими вещами: |
||
type FilePath = String |
<code>type FilePath = String |
||
openFile :: FilePath - |
openFile :: FilePath -> IOMode -> IO Handle |
||
hClose :: Handle - |
hClose :: Handle -> IO () |
||
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode |
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode</code> |
||
Далее приводится пример программы, которая копирует один файл в другой: |
Далее приводится пример программы, которая копирует один файл в другой: |
||
main = do fromHandle |
<code>main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode |
||
toHandle <- getAndOpenFile "Copy to: " WriteMode |
|||
contents <- hGetContents fromHandle |
|||
hPutStr toHandle contents |
|||
hClose toHandle |
|||
hClose fromHandle |
|||
putStr "Done." |
|||
getAndOpenFile :: String - |
getAndOpenFile :: String -> IOMode -> IO Handle |
||
getAndOpenFile prompt mode = do putStr prompt |
getAndOpenFile prompt mode = do putStr prompt |
||
name <- getLine |
|||
catch (openFile name mode) (\_ -> do putStrLn ("Cannot open " ++ name ++ "\n") getAndOpenFile prompt mode)</code> |
|||
Здесь использована одна интересная и важная функция — hGetContents, которая берёт содержимое переданного ей в качестве аргумента файла и возвращает его в качестве одной длинной строки. |
Здесь использована одна интересная и важная функция — <code>hGetContents</code>, которая берёт содержимое переданного ей в качестве аргумента файла и возвращает его в качестве одной длинной строки. |
||
== Окончательные замечания == |
== Окончательные замечания == |
||
Получается так, что в |
Получается так, что в Haskell заново изобретено императивное программирование… |
||
В некотором смысле — да. Монада IO встраивает в Haskell маленький императивный подъязык, при помощи которого можно осуществлять операции ввода |
В некотором смысле — да. Монада <code>IO</code> встраивает в Haskell маленький императивный подъязык, при помощи которого можно осуществлять операции ввода-вывода. И написание программ на этом подъязыке выглядит обычно с точки зрения императивных языков. Но есть существенное различие: в Haskell нет специального синтаксиса для ввода в программный код императивных функций, всё осуществляется на уровне функциональной парадигмы. В то же время опытные программисты могут минимизировать императивный код, используя монаду <code>IO</code> только на верхних уровнях своих программ, так как в Haskell императивный и функциональный миры чётко разделены между собой. В отличие от Haskell, в императивных языках, в которых есть функциональные подъязыки, нет чёткого разделения между обозначенными мирами. |
Версия от 11:31, 3 февраля 2008
Основы функционального программирования
- Вводная лекция
- Структуры данных и базисные операции:
В Haskell, как и во всех остальных языках, существует всесторонняя система операций ввода-вывода. Хотя обычно операции ввода-вывода предполагают некоторую последовательность в своём выполнении, то есть по сути дела императивность, в Haskell система операций ввода-вывода полностью поддерживает функциональную парадигму программирования.
Уже говорилось, что все операции ввода-вывода построены при помощи такого понятия языка Haskell, как монада (лекция 6). В то же время для понимания системы операций ввода-вывода в Haskell нет особой необходимости понимать теоретические основы понятия монада. Монады можно рассматривать как концептуальные рамки, в которых содержится система ввода-вывода. Можно сказать, что понимание теории категорий так же необходимо для использования системы операций ввода-вывода в Haskell, как и понимание теории групп для выполнения арифметических операций.
Операции ввода-вывода в любом языке основаны на понятии действия. При возбуждении действия оно выполняется. Однако в Haskell действия не возбуждаются, а скорее просто декларируются. В свою очередь действия могут быть атомарными или составленными из последовательности других действий. Монада IO
содержит операции, которые позволяют создавать сложные действия из атомарных. То есть монаду в данном случае можно рассматривать как клей, который связывает действия в программе.
Базовые операции ввода-вывода
Каждое действие ввода-вывода возвращает какое-то значение. Для того, чтобы различать эти значения от базовых, типы этих значений как бы обёрнуты типом IO
(необходимо помнить, что монада является контейнерным типом). Например, тип функции getChar
следующий:
getChar :: IO Char
В этом примере показано, что функция getChar
выполняет некоторое действие, которое возвращает значение типа Char
. Действия, которые не возвращают ничего интересного, имеют тип IO ()
. То есть символ ()
обозначает пустой тип (void
в других языках). Так функция putChar
имеет тип:
putChar :: Char -> IO ()
Друг с другом действия связываются при помощи оператора связывания. То есть символы >>=
выстраивают последовательность действий. Как известно, вместо этой функции можно использовать служебное слово do
. Оно использует такой же двумерный синтаксис, как и слова let
и where
, поэтому можно не использовать для разделения вызова функций символ ;
. При помощи слова do
можно связывать вызовы функций, определение данных (при помощи символов ->
) и множество определений локальных переменных (служебное слово let
).
Например, так можно определить программу, которая читает символ с клавиатуры и выводит его на экран:
main :: IO ()
main = do c <- getChar
putChar c
В этом примере не случайно для имени функции выбрано слово main
. В Haskell, также, как и в языках Си/Си++ название функции main
используется для обозначения точки входа в программу. Кроме того, в Haskell тип функции main
должен быть типом монады IO
(обычно используется IO ()
). Ко всему прочему, точка входа в виде функции main
должна быть определена в модуле с именем Main
.
Пусть имеется функция ready
, которая должна возвращать True
, если нажата клавиша «y», и False
в остальных случаях. Нельзя просто написать:
ready :: IO Bool
ready = do c <- getChar
c == 'y'
Потому что в этом случае результатом выполнения операции сравнения будет значение типа Bool
, а не IO Bool
. Для того, чтобы возвращать монадические значения, существует специальная функция return
, которая из простого типа данных делает монадический. То есть в предыдущем примере последняя строка определения функции ready
должна была выглядеть как return (c == 'y')
.
В следующем примере показана более сложная функция, которая считывает строку символов с клавиатуры:
Пример 16. Функция getLine
.
getLine :: IO String
getLine = do c <- getChar
if c == '\n' then return ""
else do l <- getLine
return (c : l)
Необходимо помнить, что в тот момент, когда программист перешёл в мир действий (использовал систему операций ввода-вывода), назад пути нет. То есть если функция не использует монадический тип IO
, то она не может заниматься вводом/выводом, и наоборот, если функция возвращает монадический тип IO
, то она должна подчиняться парадигме действий в Haskell.
Программирование при помощи действий
Действия ввода-вывода являются обычными значениями в терминах Haskell. То есть действия можно передавать в функции в качестве параметров, заключать в структуры данных и вообще использовать там, где можно использовать данные языка Haskell. В этом смысле система операций ввода-вывода является полностью функциональной. Например, можно предположить список действий:
todoList :: [IO ()]
todoList = [putChar 'a',
do putChar 'b'
putChar 'c',
do c <- getChar
putChar 'c']
Этот список не возбуждает никаких действий, он просто содержит их описания. Для того, чтобы выполнить эту структуру, то есть возбудить все её действия, необходима некоторая функция (например, sequence_
):
sequence_ :: [IO ()] -> IO ()
sequence_ [] = return ()
sequence_ (a:as) = do a
sequence as
Эта функция может быть полезна для написания функции putStr
, которая выводит строку на экран:
putStr :: String -> IO ()
putStr s = sequence_ (map putChar s)
На этом примере видно явное отличие системы операций ввода-вывода языка Haskell от систем императивных языков. Если бы в каком-нибудь императивном языке была бы функция map
, она бы выполнила кучу действий. Вместо этого в Haskell просто создаётся список действий (одно для каждого символа строки), который потом обрабатывается функцией sequence_
для выполнения.
Обработка исключений
Что делать, если в процессе операций ввода-вывода возникла неординарная ситуация? Например, функция getChar
обнаружила конец файла. В этом случае произойдёт ошибка. Как и любой продвинутый язык программирования, Haskell предлагает для этих целей механизм обработки исключений. Для этого не используется какой-то специальный синтаксис, но есть специальный тип IOError
, который содержит описания всех возникаемых в процессе ввода-вывода ошибок.
Обработчик исключений имеет тип (IOError -> IO a)
, при этом функция catch
ассоциирует (связывает) обработчик исключений с набором действий:
catch :: IO a -> (IOError -> IO a) -> IO a
Аргументами этой функции являются действие (первый аргумент) и обработчик исключений (второй аргумент). Если действие выполнено успешно, то просто возвращается результат без возбуждения обработчика исключений. Если же в процессе выполнения действия возникла ошибка, то она передаётся обработчику исключений в качестве операнда типа IOError
, после чего выполняется сам обработчик.
Таким образом, можно написать более сложные функции, которые будут грамотно вести себя в случае возникновения ошибочных ситуаций:
getChar' :: IO Char
getChar' = getChar `catch` eofHandler
where eofHandler e = if isEofError e then return \'n\ else ioError e
getLine' :: IO String
getLine' = catch getLine'' (\err -> return ("Error: " ++ show err))
where getLine'' = do c <- getChar'
if c == '\n' then return ""
else do l <- getLine'
return (c : l)
В этой программе видно, что можно использовать вложенные друг в друга обработчики ошибок. В функции getChar'
отлавливается ошибка, которая возникает при обнаружении символа конца файла. Если ошибка другая, то при помощи функции ioError
она отправляется дальше и ловится обработчиком, который «сидит» в функции getLine'
. Для определённости в Haskell предусмотрен обработчик исключений по умолчанию, который находится на самом верхнем уровне вложенности. Если ошибка не поймана ни одним обработчиком, который написан в программе, то её ловит обработчик по умолчанию, который выводит на экран сообщение об ошибке и останавливает программу.
Файлы, каналы и обработчики
Для работы с файлами Haskell предоставляет все возможности, что и другие языки программирования. Однако большинство этих возможностей определены в модуле IO
, а не в Prelude
, поэтому для работы с файлами необходимо явно импортировать модуль IO
.
Открытие файла порождает обработчик (он имеет тип Handle
). Закрытие обработчика инициирует закрытие соответствующего файла. Обработчики могут быть также ассоциированы с каналами, то есть портами взаимодействия, которые не связаны напрямую с файлами. В Haskell предопределены три таких канала — stdin
(стандартный канал ввода), stdout
(стандартный канал вывода) и stderr
(стандартный канал вывода сообщений об ошибках).
Таким образом, для использования файлов можно пользоваться следующими вещами:
type FilePath = String
openFile :: FilePath -> IOMode -> IO Handle
hClose :: Handle -> IO ()
data IOMode = ReadMode | WriteMode | AppendMode | ReadWriteMode
Далее приводится пример программы, которая копирует один файл в другой:
main = do fromHandle <- getAndOpenFile "Copy from: " ReadMode
toHandle <- getAndOpenFile "Copy to: " WriteMode
contents <- hGetContents fromHandle
hPutStr toHandle contents
hClose toHandle
hClose fromHandle
putStr "Done."
getAndOpenFile :: String -> IOMode -> IO Handle
getAndOpenFile prompt mode = do putStr prompt
name <- getLine
catch (openFile name mode) (\_ -> do putStrLn ("Cannot open " ++ name ++ "\n") getAndOpenFile prompt mode)
Здесь использована одна интересная и важная функция — hGetContents
, которая берёт содержимое переданного ей в качестве аргумента файла и возвращает его в качестве одной длинной строки.
Окончательные замечания
Получается так, что в Haskell заново изобретено императивное программирование…
В некотором смысле — да. Монада IO
встраивает в Haskell маленький императивный подъязык, при помощи которого можно осуществлять операции ввода-вывода. И написание программ на этом подъязыке выглядит обычно с точки зрения императивных языков. Но есть существенное различие: в Haskell нет специального синтаксиса для ввода в программный код императивных функций, всё осуществляется на уровне функциональной парадигмы. В то же время опытные программисты могут минимизировать императивный код, используя монаду IO
только на верхних уровнях своих программ, так как в Haskell императивный и функциональный миры чётко разделены между собой. В отличие от Haskell, в императивных языках, в которых есть функциональные подъязыки, нет чёткого разделения между обозначенными мирами.