монады в сахаре
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-01-20 01:13 pm (UTC)class Functor (f : Type -> Type) where fmap : (a -> b) -> f a -> f b class Functor f => Applicative (f : Type -> Type) where pure : a -> f a (<$>) : f (a -> b) -> f a -> f b class Applicative m => Monad (m : Type -> Type) where return : a -> m a (>>=) : m a -> (a -> m b) -> m b flatten : Monad m => m (m a) -> m a flatten a = a >>= idКлассические примеры подходящих конструкторов: List, Maybe, IO. Это все функторы, для которых можно описать соответствующие реализации упомянутых выше требуемых тайпклассом методов. В случае IO это оказывается хороший способ обеспечить нужный порядок выполнения эффектов вне зависимости от ленивости/строгости языка.
no subject
Date: 2013-01-20 07:30 pm (UTC)все упомянутые примеры (List, Maybe, IO) в терминах сишарпа - это generic classes, параметризованные двумя типами. ещё какая-нибудь специфика есть, или это всё?
no subject
Date: 2013-01-21 05:20 am (UTC)(return x) >>= f ≡ f x
m >>= return ≡ m
(m >>= f) >>= g ≡ m >>= ( \x -> (f x >>= g) )
Тут ≡ означает эквивалентность функций. Т.е. не всякий generic class монада, а только тот, для которого подобающим образом определены такие вот функции.
no subject
Date: 2013-01-21 09:56 am (UTC)Генерик класс в смысле ООП - это совсем не функтор. Они хоть и отображают "классы", но недостаёт способа отображать методы.
Вот если бы был фантастический ООП язык, в котором у "класса" List[Int] появлялись методы increment, +, * и константа 0, волшебным образом позаимствованные у Int и доопределённые для списка; а у "класса" List[String] появлялись методы append, charAt, ... и константа "пустая строка"; и у класса List[List[Int]] появлялись методы increment, +, *, ........ позаимствованные у List[Int] и доопределённые до List[List[Int]] и т.д., рекурсивно, вот ТОГДА это был бы функтор - то есть, когда этот генерик класс не только умеет строить списки определённого типа, но и дополняет список методов, заимствуя методы у параметра.
Это не означает, что функтор вот такой вот и есть; это лишь эскиз, как он мог бы более-менее похоже выглядеть в ОО языке. Понять функтор с точки зрения не функционального языка трудно по той причине, что даже в описанном выше виде List[Int] отображает только методы собственно Int, а не все-все функции, которые принимают Int как аргумент. По той же причине и "польза" в ОО языке была бы микроскопической - ну или по-крайней мере она не масштабируется с количеством написанных программ.
В ОО языках прямого аналога этому нет; да даже и кривого аналога "автодополнения" методов нет. В известных мне ОО языках даже List[Int] не является классом отдельным от List[String], поэтому в кавычках. С этим ничего нельзя поделать; нужно как-то въезжать, больше ничего не остаётся.
no subject
Date: 2013-01-21 10:33 am (UTC)no subject
Date: 2013-01-21 10:44 am (UTC)no subject
Date: 2013-01-21 10:52 am (UTC)no subject
Date: 2013-01-21 10:58 am (UTC)no subject
Date: 2013-01-21 11:09 am (UTC)