Насколько действительно аппликативно применять, а не "комбинировать"?
Для распространения неопределенности Approximate
типа, я хотел бы иметь экземпляры для Functor
через Monad
, Это, однако, не работает, потому что мне нужна структура векторного пространства для содержащихся типов, поэтому на самом деле это должны быть ограниченные версии классов. Поскольку до сих пор не существует стандартной библиотеки для них (или есть? Пожалуйста, укажите мне. Есть Rmonad, но он использует *
скорее, чем Constraint
как вид контекста, который кажется мне просто устаревшим), я пока что написал свою собственную версию.
Все работает легко для Functor
class CFunctor f where
type CFunctorCtxt f a :: Constraint
cfmap :: (CFunctorCtxt f a, CFunctorCtxt f b) => (a -> b) -> f a -> f b
instance CFunctor Approximate where
type CFunctorCtxt Approximate a = FScalarBasisSpace a
f `cfmap` Approximate v us = Approximate v' us'
where v' = f v
us' = ...
но прямой перевод Applicative
, лайк
class CFunctor f => CApplicative' f where
type CApplicative'Ctxt f a :: Constraint
cpure' :: (CApplicative'Ctxt f a) => a -> f a
(#<*>#) :: ( CApplicative'Ctxt f a
, CApplicative'Ctxt f (a->b)
, CApplicative'Ctxt f b) => f(a->b) -> f a -> f b
невозможно, потому что функции a->b
не имеют необходимой структуры векторного пространства * FScalarBasisSpace
,
Что работает, однако, это изменить определение ограниченного аппликативного класса:
class CFunctor f => CApplicative f where
type CApplicativeCtxt f a :: Constraint
cpure :: CAppFunctorCtxt f a => a -> f a
cliftA2 :: ( CAppFunctorCtxt f a
, CAppFunctorCtxt f b
, CAppFunctorCtxt f c ) => (a->b->c) -> f a -> f b -> f c
а затем определяя <*>#
скорее, чем cliftA2
как бесплатная функция
(<*>#) = cliftA2 ($)
вместо метода. Без ограничений это полностью эквивалентно (на самом деле, многие Applicative
в любом случае дела идут именно так), но в этом случае лучше: (<*>#)
по-прежнему имеет ограничение на a->b
который Approximate
не может выполнить, но это не повредит аппликативному экземпляру, и я все еще могу делать полезные вещи, такие как
ghci> cliftA2 (\x y -> (x+y)/x^2) (3±0.2) (5±0.3) :: Approximate Double
0.8888888888888888 +/- 0.10301238090045711
Я считаю, что ситуация была бы по существу такой же для многих других применений CApplicative
например, Set
пример, который уже приведен в оригинальном сообщении в блоге о видах ограничений.
Итак, мой вопрос:
является <*>
более фундаментальный, чем liftA2
?
Опять же, в случае без ограничений они в любом случае эквивалентны. Я на самом деле нашел liftA2
легче понять, но в Haskell, вероятно, более естественно думать о передаче "контейнеров функций", а не контейнеров объектов и некоторой "глобальной" операции для их объединения. А также <*>
непосредственно вызывает все liftAμ
для μ ∊ ℕ, а не только liftA2
; делать это из liftA2
только на самом деле не работает.
Но тогда эти ограниченные классы, кажется, дают liftA2
, В частности, это позволяет CApplicative
экземпляры для всех CMonad
с, который не работает, когда <*>#
это базовый метод. И я думаю, что мы все согласны с тем, что Applicative
всегда должен быть более общим, чем Monad
,
Что теоретики категории скажут на все это? И есть ли способ получить общее liftAμ
без a->b
необходимо выполнить связанное ограничение?
* У линейных функций такого типа действительно есть структура векторного пространства, но я определенно не могу ограничиться ими.
1 ответ
Как я понимаю (как теоретик без категории), фундаментальная операция zip :: f a -> f b -> f (a, b)
(отображение пары эффективных вычислений в эффективные вычисления, приводящие к паре).
Вы можете определить
fx <*> fy = uncurry ($) <$> zip fx fy
liftA2 g fx fy = uncurry g <$> zip fx fy
Смотрите этот пост Эдварда Янга, который я нашел через Typeclassopedia.