Haskell. Монады. Монадные трансформеры. Игра в типы

в 23:08, , рубрики: haskell, Монадные трансформеры, монады, функциональное программирование

Еще одно введение в монады для совсем совсем начинающих.

Лучший способ понять монады — это начать их использовать. Нужно забить на монадические законы, теорию категорий, и просто начать писать код.

Написание кода на Haskell похоже на игру, в которой вы должны преобразовать объекты к нужному типу. Поэтому вам в первую очередь нужно понять правила этой игры. При написании кода вы должны четко понимать, какой тип имеет каждый конкретный кусок кода.

С обычными функциями все понятно. Если имеется функция типа «a->b», то подставив в неё аргумент типа «a», вы получите результат типа «b».

С монадами все не так очевидно. Под катом подробно расписано, как работать с do-конструкцией, как последовательно преобразуются типы, и зачем нужны монадные трансформеры.

1. Do-конструкция

Начнем с простого примера.

main = do 
	putStr "Enter your namen"
	name <- getLine	
	putStr $ "Hello " ++ name

Каждая do-конструкция имеет тип «m a», где «m» — это монада. В нашем случае это монада IO.
Haskell. Монады. Монадные трансформеры. Игра в типы - 1

Каждая строчка в do-конструкции так же имеет тип «m a». Значение «a» в каждой строчке может быть разным.
Haskell. Монады. Монадные трансформеры. Игра в типы - 2
Символ "<-", как бы, преобразует тип «IO String» в тип «String».

Если нам необходимо произвести в монаде некоторые вычисления, не связанные с данной монадой, то мы можем воспользоваться функцией return.

return :: a -> m a

main = do 
	text <- getLine 
	doubleText <- return $ text ++ text
 	putStr doubleText 

Функция return заворачивает любой тип «a» в монадический тип «m a».
Haskell. Монады. Монадные трансформеры. Игра в типы - 3
В данном примере, с помощью return выражение типа «String» преобразуется к типу «IO String», которое потом обратно разворачивается в «String». Как вариант, внутри do-конструкции можно использовать ключевое слово let.

main = do 
	text <- getLine 
	let doubleText = text ++ text
 	putStr doubleText

Вся do-конструкция принимает тип последней строчки.
Haskell. Монады. Монадные трансформеры. Игра в типы - 4

Допустим, мы хотим прочитать содержимое файла. Для этого у нас имеется функция readFile.

readFile :: FilePath -> IO String

Как видим, функция возвращает «IO String». Но нам нужно содержимое файла в виде «String». Это значит, что мы должны выполнить нашу функцию внутри do-конструкции.

printFileContent = do
	fileContent <- readFile "someFile.txt" 
	putStr fileContent

Здесь переменная fileContent имеет тип «String», и мы можем работать с ней, как с обычной строкой (например, вывести на экран). Обратите внимание, что получившаяся функция printFileContent имеет тип «IO ()»

printFileContent :: IO ()
2. Монады и монадные трансформеры

Я приведу следующую простую аналогию. Представьте, что монада — это пространство, внутри которого можно производить некоторые, специфичные для данного пространства, действия.
Например, в монаде «IO» можно выводить текст в консоль.
Haskell. Монады. Монадные трансформеры. Игра в типы - 5

main = do 
	print "Hello"

В монаде «State» есть некоторое внешнее состояние, которое мы можем модифицировать.
Haskell. Монады. Монадные трансформеры. Игра в типы - 6

main = do 
	let r = runState (do 
		modify (+1)
		modify (*2)
		modify (+3)
		) 5
	print r

-- OUTPUT: 
--      ((), 15)

В этом примере мы взяли число 5, прибавили к нему 1, умножили результат на 2, затем прибавили еще 3. В результате получили число 15.

С помощью функции runState

runState :: State s a -> s -> (a, s)

мы «запускаем» нашу монаду.
Haskell. Монады. Монадные трансформеры. Игра в типы - 7

На монаду можно посмотреть с двух сторон: изнутри и снаружи. Изнутри мы можем выполнить некоторые, специфичные для данной монады, действия. А снаружи — мы можем её «запустить», «распечатать», преобразовать к некоторому немонадическому типу.

Это позволяет нам вкладывать одну do-конструкцию в другую, как в приведенном выше примере. Монада IO — это единственная монада, на которую нельзя посмотреть «снаружи». Все в конечном итоге оказывается вложенным в IO. Монада IO — это наш фундамент.

Приведенный выше пример имеет определенные ограничения. Внутри монады State мы не можем выполнять действия, доступные в IO.
Haskell. Монады. Монадные трансформеры. Игра в типы - 8
Мы оказались «подвешенными в воздухе», потеряли связь с землей.

Для решения этой проблемы существуют монадные трансформеры.

main = do 
	r <- runStateT (do 
		modify (+1)
		modify (*2)
		s <- get
		lift $ print s 
		modify (+3)
		) 5
	print r

-- OUTPUT:
--    12 
--    ((), 15)

Данная программа делает то же самое, что и предыдущая. Мы заменили State на StateT и добавили две строчки,

s <- get
lift $ print s 

с помощью которых выводим промежуточный результат в консоль. Обратите внимание, операция ввода/вывода выполняется внутри «вложенной» монады StateT.

Здесь runStateT запускает монаду StateT, а функция lift «поднимает» операцию, доступную в IO, до монады StateT.

runStateT :: StateT s m a -> s -> m (a, s)
lift :: IO a -> StateT s IO a

Изучите внимательно, как последовательно преобразуется тип в данном примере.
Haskell. Монады. Монадные трансформеры. Игра в типы - 9
Операция «print s» имеет тип «IO ()».
С помощью lift мы «поднимаем» его до типа «StateT Int IO ()».
Внутренняя do-конструкция теперь имеет тип «StateT Int IO ()».
Мы «запускаем» её и получаем тип «Int -> IO ((), Int)».
Затем мы подставляем значение «5» и получаем тип «IO ((), Int)».
Поскольку, мы получили тип «IO», то мы можем использовать его во внешней do-конструкции.
Стрелочка "<-" снимает монадический тип и возвращает "((), Int)".
В консоль выводится результат "((), 15)".

Внутри StateT мы можем менять внешнее состояние и выполнять операции ввода/вывода. Т.е. монада StateT не «болтается в воздухе», как State, а осталась связанной с внешней монадой IO.
Haskell. Монады. Монадные трансформеры. Игра в типы - 10

Таким образом, в программе может быть куча монад, вложенных друг в друга. Некоторые из этих монад будут сцеплены друг с другом, некоторые — нет.

Надеюсь, моя аналогия помогла Вам взглянуть на вещи с новой точки зрения, и Вы сможете в будущем стать настоящим повелителем монад.
Haskell. Монады. Монадные трансформеры. Игра в типы - 11

Автор: Tazman

Источник


* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js