апликативное программирование 6
Mar. 10th, 2012 03:46 pm![[personal profile]](https://www.dreamwidth.org/img/silk/identity/user.png)
1, 2, 3, 4, 5, 6, 7, 8, 9, 10
В предыдущей серии мы познакомились с аппликативным функтором.
Чем эти аппликативные функторы хороши - тем, что композиция двух аппликативных функторов - аппликативный функтор. Вот возьмём
и представим, что
Тут немножко неуклюже выглядит вызов двух pure
то можно переписать наш код красивее:
даже и не заметно, что у нас тут композиция двух монад (не забываем, что эта композиция уже не монада, вообще говоря, и в нашем специфическом примере в частности).
Чтобы функтор был аппликативным, мы должны были определить
Для монады, грубо говоря, это преобразование строится автоматически, а в общем случае его где-то надо достать. Альтернативно, можно иметь
Откуда такая эквивалентность?
Если есть
Т.е. силой загоняем функцию и значение внутрь монады, там применяем функцию к значению, а снаружи-то уже монада.
Ну и наоборот, если есть аппликативность
Мы тут, можно сказать, теорему доказали; спасибо
akuklev, подсказал.
Вот эта сила,
Вообще говоря, в математике не всякая монада сильна; но об этом, наверное, в следующий раз, с детальным примером.
В предыдущей серии мы познакомились с аппликативным функтором.
Чем эти аппликативные функторы хороши - тем, что композиция двух аппликативных функторов - аппликативный функтор. Вот возьмём
def process (user: User) (phone:Phone) = { val s = user.name + ": " + phone.number; println(s); s } val result: Set[String] = AppSet.pure(process) <*> db.getUser(userId) <*> db.getPhones(userId)
и представим, что
db.getSomething(userId)
возвращает не Set[Something]
, а Future[Set[Something]]
- обещание вернуть что-то, когда готово будет. Тогда и результат наш будет иметь формуval result: Future[Set[String]] = AppFuture.pure(AppSet.pure(process)) <*> db.getUser(userId) <*> db.getPhones(userId)
Тут немножко неуклюже выглядит вызов двух pure
AppFuture.pure(AppSet.pure(process))
; но на это в скале есть неявные функции. Если мы определим implicit def singleton[T](t: T) = AppSet.pure(t) implicit def toFuture[T](t: T) = AppFuture.pure(t)
то можно переписать наш код красивее:
val result = process <*> db.getUser(userId) <*> db.getPhones(userId)
даже и не заметно, что у нас тут композиция двух монад (не забываем, что эта композиция уже не монада, вообще говоря, и в нашем специфическом примере в частности).
Чтобы функтор был аппликативным, мы должны были определить
def ap[A, B](f: F[A => B], a: F[A]): F[B]
Для монады, грубо говоря, это преобразование строится автоматически, а в общем случае его где-то надо достать. Альтернативно, можно иметь
def strength[A, B](F[A], F[B]): F[A × B]
A × B
- такого обозначения в Скале нету на самом деле; есть Pair[A, B]
, aka Tuple2[A, B]
- класс пар; ну мы ж все понимаем, что это декартово произведение и есть, и формально надо было написатьdef strength[A, B](Pair[F[A], F[B]]): F[Pair[A, B]]
Откуда такая эквивалентность?
Если есть
strength
, то можно определитьdef ap[A, B](ff: F[A => B], fa: F[A]): F[B] = strength(ff, fa).map((f, a) => f(a))
Т.е. силой загоняем функцию и значение внутрь монады, там применяем функцию к значению, а снаружи-то уже монада.
Ну и наоборот, если есть аппликативность
ap
, определимdef pairing[A, B](a: A): (B => Pair[A, B]) = b => (a, b) // функция такая def pairingLifted[A, B](fa: F[A]): (F[B] => F[Pair[A, B]]) = fa map pairing // та же функция, да на F[A] def strength[A, B](fa: F[A], fb: F[B]): F[Pair[A, B]] = ap(pairingLifted, fa) // QED
Мы тут, можно сказать, теорему доказали; спасибо
![[livejournal.com profile]](https://www.dreamwidth.org/img/external/lj-userinfo.gif)
Вот эта сила,
strentgh
, традиционно называется монадической силой, если речь идёт о монадах. И в компьютерной науке есть поверие, что всякая монада обладает монадической силой - ну просто мы подымем бинарную операцию в монаду, и всё получится.Вообще говоря, в математике не всякая монада сильна; но об этом, наверное, в следующий раз, с детальным примером.
no subject
Date: 2012-03-11 12:02 am (UTC)А ещё интересный вопрос -- как завернуть рекурсивно вычисляемое значение в монаду и что от этого произойдёт с эффектом этой монады. Ну то есть в Хаскеле-то есть mdo-notation уже лет 10 как, а вот есть ли это в Scala, я не знаю.
no subject
Date: 2012-03-11 12:06 am (UTC)Про mdo ничего не знаю, придётся ознакомиться. Спасибо.
no subject
Date: 2012-03-11 12:17 am (UTC)no subject
Date: 2012-03-11 12:47 am (UTC)no subject
Date: 2012-03-11 12:59 am (UTC)Теперь давай предположим, что к значению мы доступа не имеем, то есть есть только pure и fmap и мы можем писать в аппликативном стиле. Понятно, что рекурсивные опеределния в этом случае будет оперировать не с самими значениями, а с их аппликативными образами. Что тогда с эффектами? Должны ли они накапливаться или должны вычислять только раз, как в случае с рекурсивной монадой?
no subject
Date: 2012-03-11 01:27 am (UTC)Но вообще, надо снова будет прочитать МакБрайда и Патерсона. У них там как-то подозрительно непринужденно нарисованы эти транспонирования матриц...
no subject
Date: 2012-03-12 10:52 pm (UTC)no subject
Date: 2012-03-11 08:34 am (UTC)Ну и если в Scala for - это do-нотация, то рекурсивные определения там не поддерживаются. Впрочем, если писать mfix руками, то, конечно, сделать это можно. Но это уже будет не так красиво (мы избавляемся от рекурсивного определения).
no subject
Date: 2012-03-11 06:21 am (UTC)no subject
Date: 2012-03-11 07:23 am (UTC)no subject
Date: 2012-03-11 07:28 am (UTC)no subject
Date: 2012-03-11 08:11 am (UTC)А зачем? Может писать более generic? Пусть это будет
где A - это нужный аппликативный функтор. Future(Set(process)) вынудит нас использовать аппликативный функтор для Future[Set[_]]. Может в другом месте мы в виде списка захотим получить значения из базы, а в другом в виде Option.
no subject
Date: 2012-03-11 08:19 am (UTC)no subject
Date: 2012-03-11 08:20 am (UTC)no subject
Date: 2012-03-11 08:19 am (UTC)тогда вообще не придётся мучиться. Кстати, твой пример с implicit работать, к сожалению, не будет. Scala типы смотрит слева направо. Вот если бы сначала встретился db.getUser и по нему стало понятно, что мы работаем с Future-Set, то process бы обернулся implicitly, а в представленной записи будет ошибка компиляции. Не Haskell :-(
no subject
Date: 2012-03-11 09:05 am (UTC)no subject
Date: 2012-03-12 10:21 pm (UTC)no subject
Date: 2012-03-11 09:22 am (UTC)а то из второго следует F(eval_X)=eval_Y, а из первого - не очевидно.
no subject
Date: 2012-03-11 11:54 am (UTC)no subject
Date: 2012-03-12 09:43 am (UTC)тьфу, но и это ещё не всё. Это же годится для тех категорий, где tensor product is a cartesian product.
no subject
Date: 2012-03-12 10:22 pm (UTC)no subject
Date: 2012-03-13 02:15 am (UTC)На википедии в разделе, где сила в втором виде, речь идёт о commutative strong monads. Вот я и думал, что раз дополнительно упоминается "commutative", то силу в таком виде не у всех монад можно обнаружить.
Теперь почитал, выходит, что в CCC категории тензорное произведение можно всегда задать через декартово, ибо терминальный объект в A=1xA=Ax1 ведёт себя совершенно как единица тензорного произведения.
no subject
Date: 2012-03-13 02:23 am (UTC)1. Почему говорят о тензорном произведении, а пользуются декартовым. ответ: в CCC категории терминальный объект ведёт себя как единица тензорного произведения, и существуют произведения всех пар объектов, поэтому всегда можно использовать декартово произведение как тензорное.
2. Почему говорят о commutative strong monads, когда задают силу во втором виде. Точное условие этого я ещё не до конца понял, но хоть стало понятно, каким образом второе из первого вытекает. Поскольку в CCC есть декартовы произведения всех объектов, а между всеми произведениями одинаковых объектов (например, AxB и BxA) есть изоморфизмы, то в CCC всегда определен морфизм flip.
no subject
Date: 2012-03-13 03:27 am (UTC)no subject
Date: 2012-03-13 08:33 am (UTC)А можно для иллюстрации диаграмку, где тут пулбэк получается?
no subject
Date: 2012-03-13 03:46 pm (UTC)