Насколько действительно аппликативно применять, а не "комбинировать"?

Для распространения неопределенности 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.

Другие вопросы по тегам