монады в сахаре
Jan. 19th, 2013 04:42 pmНедавно видел жалобу на то, что с монадами код разрастается, и простые выражения порой требуют несколько строчек для записи (речь про do-нотацию была). Сейчас осваиваю потихоньку язык Idris - это такой правильный хаскель с полноценными зависимыми типами (о которых чуть позже) и кучей встроенных плюшек для создания eDSL'ей (об этом вероятно еще позже). Так вот, в идрисе для монад сахара еще подсыпали, хочу поделиться. Пусть у нас есть функция readInt типа String -> IO Int, запрашивающая и получающая число откуда-то из внешнего мира, и мы хотим прочитать два числа и вывести их сумму. В языках вроде Clean или ML, где для монад вообще никаких спецсредств нету, это выглядит весьма печально. В хаскеле и идрисе есть привычная всем do-нотация:
Вот и получилось много строк на ровном месте. Первый кусочек сахара: monad comprehensions. Они когда-то были в хаскеле, потом потерялись, есть ли сейчас - не знаю. Идея в том, чтобы list comprehensions обобщить с монады List на произвольные монады:
Не особо короче, чем вариант do с фигурными скобками и точками с запятой, но сама идея занятная.
Еще один вариант, доступный и в хаскеле: спрятать весь plumbing в операторы. Это как раз происходит, если вспомнить, что монада это функтор, а в наших языках еще и аппликативный:
(это на идрисе, в хаскеле некоторые значки будут другими, но в целом то же самое)
Второй кусочек сахара: idiom brackets. Специальный синтаксис для аппликативных функторов, где функции автоматически отображаются функтором с помощью pure, а их применения заменяются на применения образов через <$>:
(рассахаривается в предыдущий вариант)
В хаскеле похожая штука есть, но выглядит довольно страшно.
Еще пример для закрепления. Вот такую функцию
можно записать как
main = do a <- readInt "a" b <- readInt "b" print (a+b)
Вот и получилось много строк на ровном месте. Первый кусочек сахара: monad comprehensions. Они когда-то были в хаскеле, потом потерялись, есть ли сейчас - не знаю. Идея в том, чтобы list comprehensions обобщить с монады List на произвольные монады:
main = [a + b | a <- readInt "a", b <- readInt "b"] >>= print
Не особо короче, чем вариант do с фигурными скобками и точками с запятой, но сама идея занятная.
Еще один вариант, доступный и в хаскеле: спрятать весь plumbing в операторы. Это как раз происходит, если вспомнить, что монада это функтор, а в наших языках еще и аппликативный:
class Functor f => Applicative (f : Type -> Type) where
pure : a -> f a
(<$>) : f (a -> b) -> f a -> f b
и
main = (pure (+) <$> readInt "a" <$> readInt "b") >>= print(это на идрисе, в хаскеле некоторые значки будут другими, но в целом то же самое)
Второй кусочек сахара: idiom brackets. Специальный синтаксис для аппликативных функторов, где функции автоматически отображаются функтором с помощью pure, а их применения заменяются на применения образов через <$>:
main = [|readInt "a" + readInt "b"|] >>= print
(рассахаривается в предыдущий вариант)
В хаскеле похожая штука есть, но выглядит довольно страшно.
Еще пример для закрепления. Вот такую функцию
m_mul : Maybe Int -> Maybe Int -> Maybe Int m_mul (Just x) (Just y) = Just (x * y) m_mul _ _ = Nothing
можно записать как
m_mul x y = [| x * y |]
no subject
Date: 2013-02-04 08:59 am (UTC)А в смысле "как работает" - вот мой вариант:
Бывает (и весьма часто) что код имеет ярко выраженную линейную структуру, но при этом не сводится к простому выполнению операторов одного за другим. Например:
for a in [1..100]:
for b in [a..10]:
for c in [1..a+b]:
...ешё много for-ов
или:
a = do_something_1(&success);
if (success) {
b = do_something_2(a, &success);
if (success) {
c = do_something_3(a, b, &success);
...
}
}
Можно и ещё примеров подобрать, но суть в этом.
Правильно подобранная монада делает из этого вот что:
a <- [1..100]
b <- [a..100]
c <- [1..a+b]
...
и, соответственно,
a <- do_something_1()
b <- do_something_2(a)
c <- do_something_3(a, b)
...
Все детали прячутся в конкретную монаду. После чего оказывается, что можно - и полезно - обобщать такие вещи. То есть, писать эту линейную часть сначала, а монаду выбирать потом, соединяя эти штуки по-разному.
no subject
Date: 2013-02-04 09:29 am (UTC)отличный способ выстрелить себе в ногу, и мало что кроме этого.
но я не думаю, что цель монад - спрятать от читателя control flow.
no subject
Date: 2013-02-04 09:39 am (UTC)Во-вторых, не спрятать, а обобщить.
В-третьих, если есть явный паттерн, то оформить его в отдельную сущность - правильный подход.