Предоставляет ли интерфейс Applicative больше возможностей, чем возможность поднять функции с несколькими аргументами (в форме карри) в Functor? - PullRequest
3 голосов
/ 28 апреля 2020

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

Представьте себе, что мы определяем интерфейс на основе подъемные функции, аргумент которых является кортежем аргументов:

# from Functor
fmap :: (a -> b) -> Fa -> Fb
# from Applicative
pure :: a -> Fa

# combine multiple functor values into a functor of a tuple
tuple1 :: Fa -> F(a)
tuple2 :: Fa -> Fb -> F(a,b)
tuple3 :: Fa -> Fb -> Fc -> F(a,b,c)
(etc ...)

# lift multi-argument functions (that take a tuple as input)
ap_tuple1 :: ((a) -> b) -> F(a) -> Fb
ap_tuple2 :: ((a,b) -> c) -> F(a,b) -> Fc
ap_tuple3 :: ((a,b,c) -> d) -> F(a,b,c) -> Fd
(etc ..)

Предположим, у нас была определенная функция кортежа, определенная для каждого кортежа любого размера, с которым мы можем столкнуться. Будет ли этот интерфейс столь же мощным, как интерфейс Applicative, учитывая, что он позволяет поднимать / применять к функциям с несколькими аргументами, НО не позволяет поднимать / применять к функциям, которые возвращают функцию? Очевидно, что можно каррировать функции, которые принимают кортеж в качестве аргумента, чтобы их можно было поднять в аппликативном, и можно откатить функции, которые возвращают функцию, чтобы поднять их в гипотетическую реализацию выше. Но, на мой взгляд, есть небольшая разница в силе. Есть ли разница? (Предполагая, что вопрос даже имеет смысл)

Ответы [ 2 ]

6 голосов
/ 28 апреля 2020

Вы заново открыли моноидальное представление из Applicative. Это выглядит так:

class Functor f => Monoidal f where
    (>*<) :: f a -> f b -> f (a, b)
    unit :: f ()

Это изоморфно c до Applicative через:

(>*<) = liftA2 (,)
unit = pure ()

pure x = x <$ unit
f <*> x = fmap (uncurry ($)) (f >*< x)

Кстати, все ваши функции ap_tuple всего лишь fmap. «Жесткая» часть с несколькими значениями объединяет их вместе. Разбить их на части "легко".

3 голосов
/ 28 апреля 2020

Да, это одинаково мощно. Обратите внимание, что pure и tuple1 одинаковы. Кроме того, все, что выше tuple2, восстанавливается из tuple2 и fmap:

tuple3 x y z = repair <$> tuple2 (tuple2 x y) z
    where repair ((a, b), c) = (a, b, c)
tuple4 w x y z = repair <$> tuple2 (tuple2 x y) (tuple2 x y)
    where repair ((a, b), (c, d)) = (a, b, c, d)
-- etc.

Кроме того, все ap_tuple просто fmap:

ap_tuple1 = fmap
ap_tuple2 = fmap
ap_tuple3 = fmap
-- ...

Переименование prod = tuple2, ваш вопрос сводится к

Это

class Functor f => Applicative f where
    pure :: a -> f a
    prod :: f a -> f b -> f (a, b)

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

class Functor f => Applicative f where
    pure :: a -> f a
    liftA2 :: (a -> b -> c) -> f a -> f b -> f c

?

И вы уже можете видеть, что ответ - да. prod - это просто специализация liftA2

prod = liftA2 (,)

Но (,) "естественен" в том смысле, что он ничего не "удаляет", поэтому вы можете восстановить liftA2 просто деструктуризация данных обратно:

liftA2 f x y = f' <$> prod x y
    where f' (a, b) = f a b
...