Моноидальный функтор является аппликативным, но где Моноидный тип в определении Аппликативного?

Applicative является Моноидальным функтором:

mappend :: f         -> f   -> f
$       ::  (a -> b) ->   a ->   b
<*>     :: f(a -> b) -> f a -> f b

но я не вижу никакой ссылки на моноид в определении Аппликативного типа, не могли бы вы сказать мне, почему ?

определение :

class Functor f => Applicative (f :: * -> *) where
  pure :: a -> f a
  (<*>) :: f (a -> b) -> f a -> f b
  GHC.Base.liftA2 :: (a -> b -> c) -> f a -> f b -> f c
  (*>) :: f a -> f b -> f b
  (<*) :: f a -> f b -> f a
  {-# MINIMAL pure, ((<*>) | liftA2) #-}

в этом определении не упоминается этот структурный моноид, но когда вы это делаете

> ("ab",(+1)) <*> ("cd", 5) 
>("abcd", 6)

вы можете ясно видеть использование структурного моноида "(,) String " при реализации этого экземпляра Applicative.

другой пример, чтобы показать, что используется "структурный моноид":

Prelude Data.Monoid> (2::Integer,(+1)) <*> (1::Integer,5)

<interactive>:35:1: error:
    • Could not deduce (Monoid Integer) arising from a use of ‘<*>’
      from the context: Num b
        bound by the inferred type of it :: Num b => (Integer, b)
        at <interactive>:35:1-36
    • In the expression: (2 :: Integer, (+ 1)) <*> (1 :: Integer, 5)
      In an equation for ‘it’:
          it = (2 :: Integer, (+ 1)) <*> (1 :: Integer, 5)

3 ответов


моноид, который упоминается с "моноидальным функтором", не является Monoid моноид, т. е. моноид уровня значения. Это моноид уровня типа. А именно, моноид скучного продукта

type Mempty = ()
type a <> b = (a,b)

(вы можете заметить, что это не строго говоря моноид; это только если вы считаете ((a,b),c) и (a,(b,c)) как один и тот же тип. Они достаточно изоморфны.)

чтобы узнать, какое это имеет отношение к Applicative, респ. моноидальные функторы, нам нужно написать класс в других терминах.

class Functor f => Monoidal f where
  pureUnit :: f Mempty
  fzip :: f a -> f b -> f (a<>b)

-- an even more “general nonsense”, equivalent formulation is
-- upure :: Mempty -> f Mempty
-- fzipt :: (f a<>f b) -> f (a<>b)
-- i.e. the functor maps a monoid to a monoid (in this case the same monoid).
-- That's really the mathematical idea behind this all.

IOW

class Functor f => Monoidal f where
  pureUnit :: f ()
  fzip :: f a -> f b -> f (a,b)

это простое упражнение для определения общего экземпляра стандарта Applicative класса Monoidal, наоборот.


о ("ab",(+1)) <*> ("cd", 5): это не имеет большого отношения к Applicative в общем, но только с писателем applicative конкретно. Экземпляр

instance Monoid a => Monoidal ((,) a) where
  pureUnit = (mempty, ())
  fzip (p,a) (q,b) = (p<>q, (a,b))

возможно, моноиде вы почувствуете это.

newtype AppM f m = AppM (f m) deriving Show

instance (Applicative f, Monoid m) => Monoid (AppM f m) where
  mempty                      = AppM (pure mempty)
  mappend (AppM fx) (AppM fy) = AppM (pure mappend <*> fx <*> fy)

как комментарий, ниже, замечает, его можно найти в редуктор библиотека под названием Ap. Это фундаментально для Applicative, так что давайте разбираться.

обратите внимание, в частности, что, поскольку () тривиальноMonoid, AppM f () это Monoid тоже. И это моноид, скрывающийся позади Applicative f.

мы могли бы настоять на Monoid (f ()) как суперкласс Applicative, но это бы все испортило по-королевски.

> mappend (AppM [(),()]) (AppM [(),(),()])
AppM [(),(),(),(),(),()]

в моноиде базовых Applicative [] is умножение натуральных чисел, тогда как "очевидной" моноидальной структурой для списков является конкатенация, которая специализируется на дополнение натуральных чисел.

математика предупреждение. Предупреждение зависимых типов. Поддельное предупреждение Хаскелла.

один из способов увидеть, что происходит, - рассмотреть эти Аппликаторы, которые бывают тара в зависимо напечатанном смысле Эббота, Альтенкирха и Гани. Мы в Haskell в ближайшее время. Я просто притворюсь, что будущее наступило.

data (<|) (s :: *)(p :: s -> *) (x :: *) where
  (:<|:) :: pi (a :: s) -> (p a -> x) -> (s <| p) x

структуры данных (s <| p) характеризуется

  • формы s которые говорят вам, как выглядит контейнер.
  • позиции p что вам сказать для данного форма где вы можете поместить данные.

вышеуказанный тип говорит, что дать данные для такой структуры-это выбрать форму, а затем заполнить все позиции данными.

представление контейнера [] is Nat <| Fin здесь

data Nat = Z | S Nat
data Fin (n :: Nat) where
  FZ :: Fin (S n)
  FS :: Fin n -> Fin (S n)

, так что Fin n ровно n значения. То есть, форма списка - это его длина, и это говорит вам, сколько элементов вам нужно заполнить список.

вы можете найти формы для Haskell Functor f взяв f (). Делая данные тривиальными, позиции не имеют значения. Построение GADT позиций в целом в Haskell довольно сложно.

Parametricity говорит нам, что полиморфные функции между контейнерами в

forall x. (s <| p) x -> (s' <| p') x

должно быть дано

  • функция f :: s -> s' отображение входных фигур в выходные фигуры
  • функция g :: pi (a :: s) -> p' (f a) -> p a отображение (для заданного ввода shape) выходные позиции вернуться к входным позициям, откуда будет поступать выходной элемент.

morph f g (a :<|: d) = f a :<|: (d . g a)

думая в том же духе, что нужно, чтобы сделать контейнер Applicative? Для стартеры,

pure :: x -> (s <| p) x

что эквивалентно

pure :: (() <| Const ()) x -> (s <| p) x

это должно быть дано

f :: () -> s   -- a constant in s
g :: pi (a :: ()) -> p (f ()) -> Const () a  -- trivial

здесь f = const neutral для некоторых

neutral :: s

теперь насчет

(<*>) :: (s <| p) (x -> y) -> (s <| p) x -> (s <| p) y

? Опять же, parametricity говорит нам две вещи. Во-первых, единственными полезными данными для вычисления выходных фигур являются две входные фигуры. У нас должна быть функция

outShape :: s -> s -> s

Secondly, единственный путь мы можем заполнить положение выхода с a y - выбрать позицию из первого входа, чтобы найти функцию в "x - > y", а затем позицию во втором входе, чтобы получить ее аргумент.

inPos :: pi (a :: s)(b :: s) -> p (outShape a b) -> (p a, p b)

то есть мы всегда можем определить пару входных позиций, которые определяют выход в выходной позиции.

прикладные законы говорят нам, что neutral и outShape должны подчиняться законам моноидом, и, что более того, мы можем поднять моноиды следующим образом

mappend (a :<|: f) (b :<|: g) = outShape a b :<|: \ z ->
  let (x, y) = inPos a b z
  in  mappend (f x) (g y)

есть кое-что еще скажем, здесь, но для этого мне нужно сравнить две операции с контейнерами.

состав

(s <| p) . (s' <| p')  =  ((s <| p) s') <| \ (a :<|: f) -> Sigma (p a) (p' . f)

здесь Sigma тип зависимых пар

data Sigma (p :: *)(q :: p -> *) where
  Pair :: pi (a :: p) -> q a -> Sigma p q

что это значит?

  • вы выбираете внешнюю форму
  • вы выбираете внутреннюю форму для каждого внешнего положения
  • составное положение после этого пара наружного положения и внутреннего положения соотвествующего к внутренняя форма, которая сидит там

или, в Hancock

  • вы выбираете внешнюю команду
  • вы можете ждать, чтобы увидеть внешний ответ, прежде чем выбрать внутреннюю команду
  • составной ответ - это ответ на внешнюю команду, а затем ответ на внутреннюю команду, выбранную вашей стратегией

или, более нагло

  • когда вы составляете список списков, внутренние списки может иметь различную длину

на join на Monad плющит состав. За ним скрывается не просто моноид по форме, а интеграция оператора. То есть,

join :: ((s <| p) . (s <| p)) x -> (s <| p) x

требует

integrate :: (s <| p) s -> s

ваша свободная монада дает вам деревья стратегии, где вы можете использовать результат одной команды, чтобы выбрать остальную часть вашей стратегии. Как будто вы взаимодействуете в 1970-х телетайп.

между тем...

тензора

тензор (также из-за Хэнкока) двух контейнеров задается

(s <| p) >< (s' <| p')  =  (s, s') <| \ (a, b) -> (p a, p' b)

что это

  • вы выбираете две фигуры
  • позиция - это пара позиций, по одной для каждой фигуры

или

  • вы выбираете две команды, не видя никаких ответов
  • ответ тогда пара ответы

или

  • [] >< [] тип прямоугольных матриц: "внутренние" списки должны иметь одинаковую длину

последнее является ключом к тому, почему >< очень трудно получить ваши руки в Haskell, но легко в зависимо типизированной настройке.

как и композиция, тензор является моноидом с функтором тождества в качестве его нейтрального элемента. Если мы заменим состав, лежащий в основе Monad тензором, что мы получим?

pure :: Id x -> (s <| p) x
mystery :: ((s <| p) >< (s <| p)) x -> (s <| p) x

но все, что может mystery быть? Это не тайна, потому что мы знаем, что существует довольно жесткий способ создания полиморфных функций между контейнерами. Должно быть

f :: (s, s) -> s
g :: pi ((a, b) :: (s, s)) -> p (f (a, b)) -> (p a, p b)

и это именно то, что мы сказали определено <*> раньше.

Applicative понятие effectful программирования генерируется тензором, где Monad создается композиция. Тот факт, что вам не нужно / нужно ждать внешний ответ на выбор внутренней команды-почему Applicative программы более легко распараллелить.

видим [] >< [] как прямоугольные матрицы говорит нам, почему <*> для списков строится поверх умножения.

свободный аппликативный функтор свободный моноид с ручками дальше. Для контейнеров,

Free (s <| p) = [s] <| All p

здесь

All p [] = ()
All p (x : xs) = (p x, All p xs)

таким образом," команда " - это большой список команд, как колода перфокарт. Вы не получите, чтобы увидеть какой-либо выход прежде чем выбрать колоду карт. "Ответ" - это ваш вывод lineprinter. Это 1960-е годы.

Итак, поехали. Сама природа Applicative, тензор не состав, требует основного моноида и рекомбинации элементов, совместимых с моноидами.


я хотел дополнить Конор Макбрайда (pigworker) поучительный ответ!--33--> несколько примеров Monoids найдено в Applicatives. отмечалось что Applicative экземпляр некоторых функторов напоминает соответствующий Monoid экземпляр; например, у нас есть следующие аналогии:

Applicative → Monoid
---------------------
List        → Product
Maybe       → All
Either a    → First a
State s     → Endo s

следуя комментарию Конора, мы можем понять почему у нас есть эти соответствия. Мы используем следующее замечания:

  1. форму Applicative контейнер образует Monoid при эксплуатации <*>.
  2. форма функтора F дано F 1 (где 1 обозначает блок ()).

для каждого Applicative функторы, перечисленные выше, мы вычисляем их форму путем создания экземпляра функтора с элементом unit. Мы это понимаем...

List имеет форму Nat:

List a = μ r . 1 + a × r
List 1 = μ r . 1 + 1 × r
       ≅ μ r . 1 + r
       ≅ Nat

Maybe имеет форму Bool:

Maybe a = 1 + a
Maybe 1 = 1 + 1
        ≅ Bool

Either имеет форму Maybe:

Either a b = a + b
Either a 1 = a + 1
           ≅ Maybe a

State имеет форму Endo:

State s a = (a × s) ^ s
State s 1 = (1 × s) ^ s
          ≅ s ^ s
          ≅ Endo s

типы фигур точно соответствуют типам, лежащим в основе Monoids перечислены в начале. Одна вещь все еще озадачивает меня: некоторые из этих типов допускают несколько Monoid экземпляры (например, Bool можно сделать Monoid as All или Any) и я не совсем уверен, почему мы получаем один из экземпляров, а не другой. Я предполагаю, что это связано с прикладными законами и тем, как они взаимодействуют с другим компонентом контейнера – его позициями.