Подтвердить что ты не робот

Может кто-нибудь объяснить мне, почему функция приложения ArrowApply делает их такими же мощными, как монады?

Итак, я сломаю свой вопрос на 4 части, но сначала немного фона:

Я чувствую себя относительно комфортно с Monads, но не очень удобен с Arrows. Я полагаю, что основная проблема, с которой я сталкиваюсь, заключается в том, что я не понимаю, для чего они полезны. Формально ли это правильно или нет, я понимаю, что Monads - это инструмент, который позволяет нам вводить побочные эффекты при вычислении. Поскольку они обобщают программные фрагменты от чистых значений до значений, помещенных в коробки с другими действиями. Из моего дробовика "прочитайте все документы", чтобы узнать о стрелках, я столкнулся с двумя противоречивыми точками зрения:

а. Стрелки более мощные, чем Monads/являются обобщениями Monads. Вики "haskell" начинаются с "Они могут делать все, что могут сделать монады, и многое другое. Они примерно сопоставимы с монадами со статическим компонентом".

В. Стрелки - это подмножество Monads С помощью ArrowApply мы можем определить монаду

  • Есть ли какая-либо истина для точки зрения A?
  • Какую функциональность стрелки не имеют, я читал, что разница имеет отношение к композиции, поэтому что оператор → > позволяет нам сделать это → = не?
  • Что делает приложение точно? у этого типа нет даже (- > )
  • Почему мы хотели бы использовать аппликативные стрелки над монадами?
4b9b3361

Ответ 1

Предупреждения с несколькими интерпретаторами:

"A более мощный, чем B"... "C - это обобщение D"... "E может делать все, что может сделать F, и больше"... "G является подмножеством H"..

Сначала нам нужно понять, что мы имеем в виду под мощным и т.д. Предположим, что у нас был класс GripHandle для вещей с ручкой захвата и еще один класс Screwdriver для отверток. Что более мощно?

  • Хорошо, если у вас просто хватит рукоятки, это не так полезно, как если бы вы были отверткой; ручка для рукоятки сама по себе не очень удобна, поэтому, очевидно, потому что вы можете сделать больше с помощью отвертки, чем ручка захвата, поэтому отвертки более мощные.
  • Хорошо, что у большего количества вещей есть ручка захвата, а не только отвертки - сверла, ножи и вилки, все виды имеют рукоятки, поэтому рукоятки более мощные и гибкие.
  • Хорошо, если у вас есть отвертка, вы можете не только удерживать ее, вы можете ее повернуть, а иметь возможность поворачиваться, а не просто держать отвертки гораздо более мощными и гибкими, чем рукоятки сцепления.

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

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

Как работает иерархия

A является более общим, чем B
= A только для интерфейса - это подмножество B= вы можете сделать больше с экземпляром B (в одиночку)
= Класс всех B является подмножеством класса всех A= больше A чем B= вы можете сделать больше с классом A

более общий
= более возможные случаи
= возможность более широко использоваться
= может делать лишние вещи за кулисами
= меньше возможностей указаны в интерфейсе
= может делать меньше всего через интерфейс

Какова иерархия между стрелками и монадами?

  • Arrow более общий, чем Monad.
  • ArrowApply точно такой же общий, как Monad.

Эти два утверждения подробно описаны в статье Петра Пудлака, о которой говорится в его комментарии: Идиомы не обращают внимания, стрелки являются дотошными, монады неразборчивы.

Утверждения в и B

  • "Стрелки могут делать все, что могут сделать монады, и многое другое".
    Это маркетинг. Это правда, но вы должны немного перепрыгнуть немного семантически, чтобы сделать это правдой. Экземпляр ArrowApply сам по себе позволяет делать все, что делает экземпляр Monad сам по себе. Вы не можете сделать больше с помощью ArrowApply, чем с помощью Monad. Вы можете делать больше с вещами Arrows. Требование "все монады могут сделать", вероятно, относится к ArrowApply, в то время как требование "и больше", вероятно, относится к Arrow! Доска объявлений Monad может сказать: "С помощью Monads вы можете делать все, что могут сделать Arrows, и многое другое" из-за повышенной выразительности интерфейса. Из-за этого эти утверждения неоднозначны и имеют мало формального значения.
  • "Они примерно сопоставимы с монадами со статическим компонентом".
    Строго говоря, нет, это просто то, что вы можете сделать с Arrow, что вы не можете сделать непосредственно с Monad, а не математический факт о интерфейсе Arrow. Это способ помочь вам справиться с тем, что мы можем сделать со Стрелкой, аналогично тому, как аналог Монады является коробкой со значением. Не все монады легко интерпретируются как ящик со значением в, но это может помочь вам немного на ранних стадиях.
  • "Стрелки - это подмножество Монад"
    Возможно, это вводит в заблуждение. Верно, что только для интерфейса только стрелки - это подмножество интерфейсных возможностей Monads, но справедливо сказать, что класс всех Monads является подмножеством класса всех Arrows, потому что Arrow больше Генеральная.
  • "С помощью ArrowApply мы можем определить монаду"
    Да. См. Позже, и с помощью Monad мы можем определить ArrowApply.

Ваши четыре вопроса

  • Есть ли какая-либо истина для точки зрения A?
    Некоторые. См. Выше. Там тоже правда в B. Оба в той или иной степени вводят в заблуждение.
  • Какую функциональность стрелки не имеют, я читал, что разница имеет отношение к композиции, поэтому что оператор → > позволяет нам сделать это → = не делает
     Фактически >>= позволяет делать больше, чем >>> (больше возможностей, предоставляемых интерфейсом). Это позволяет вам переключать контекст. Это связано с тем, что Monad m => a -> m b является функцией, поэтому вы можете выполнить произвольный чистый код на входе A, прежде чем решать, какую монадическую вещь выполнять, тогда как Arrow m => m a b не является функцией, и вы решили, перед тем как вы проверите вход A.

    monadSwitch :: Monad m => m a -> m a -> (Bool -> m a)
    monadSwitch computation1 computation2 test 
          = if test then computation1 else computation2
    

    Невозможно смоделировать это с помощью Arrow без использования app из ArrowApply

  • Что делает приложение точно? у этого типа нет даже (- > )
     Он позволяет использовать вывод стрелки в виде стрелки. Давайте посмотрим на тип.

    app :: ArrowApply m => m (m b c, b) c
    

    Я предпочитаю использовать m для A, потому что m больше похож на вычисление, а A чувствует себя как значение. Некоторым людям нравится использовать оператор типа (конструктор типа infix), поэтому вы получаете

    app :: ArrowApply (~>) => (b ~> c, b) ~> c
    

    Мы думаем о b ~> c как стрелке, и мы думаем о стрелке как о вещи, которая принимает B s, что-то делает и дает c s. Таким образом, это означает, что app - стрелка, которая принимает стрелку и значение, и может генерировать значение, которое первая стрелка произвела бы на этом входе.

    Он не имеет -> в сигнатуре типа, потому что при программировании со стрелками мы можем превратить любую функцию в стрелку с помощью arr :: Arrow (~>) => (b -> c) -> b ~> c, но вы не можете превратить каждую стрелку в функцию, таким образом (b ~> c, b) ~> c можно использовать, если (b ~> c, b) -> c или (b -> c, b) ~> c не будет.

    Мы можем легко создать стрелку, которая создает стрелку или даже несколько стрелок, даже без ArrowApply, просто выполнив produceArrow :: Arrow (~>) => (b ~> c) -> (any ~> (b ~> c)), определенный с помощью produceArrow a = arr (const a). Трудность заключается в том, чтобы стрелка делала любую стрелку - как вам получить стрелу, которую вы создали, чтобы стать следующей стрелкой? Вы не можете поместить его в качестве следующего вычисления, используя >>>, как вы можете сделать с монадической функцией Monad m => a -> m b (просто сделайте id :: m a -> m a!), Потому что, в сущности, стрелки не функции, но используя app, мы можем сделать следующую стрелку, сделав то, что сделала бы стрелка, созданная предыдущей стрелкой.

    Таким образом, ArrowApply предоставляет вам исполняемую версию выполнения, созданную во время выполнения, из Monad.

  • Зачем нам когда-либо хотеть использовать аппликативные стрелки над монадами?
     Er, вы имеете в виду стрелки или аппликативные функторы? Аппликативные функторы великолепны. Они более общие, чем Monad или Arrow (см. Статью), поэтому имеют меньшую функциональность, указанную в интерфейсе, но более широко применимы (примените/примените/примените chortle chortle lol rofl category theory humor hahahaha).

    Аппликативные функторы имеют прекрасный синтаксис, который очень похож на приложение с чистой функцией. f <$> ma <*> mb <*> mc запускает ma, затем mb, затем mc и применяет чистую функцию f к трем результатам. Например. (+) <$> readLn <*> readLn читает два целых числа от пользователя и добавляет их.

    Вы можете использовать Applicative для получения общности, и вы можете использовать Monads для получения функциональности интерфейса, поэтому вы можете утверждать, что теоретически они нам не нужны, но некоторые люди любят обозначение для стрелок, потому что это похоже на обозначение, и вы действительно можете использовать Arrow для реализации парсеров, у которых есть статический компонент, поэтому используйте оптимизацию времени компиляции. Я считаю, что вы можете сделать это с помощью Applicative, но сначала это было сделано со стрелкой.

    Заметка о том, что Аппликативное "менее мощное":
     В документе указывается, что Applicative более общий, чем Monad, но вы можете сделать, чтобы Аппликативные функторы имели одинаковые возможности, предоставляя функцию run :: Applicative f => f (f b) -> f b, которая позволяет запускать произведенное вычисление или use :: Applicative f => f (a -> f b) -> f a -> f b, что позволяет вам продвигать полученное вычисление к вычислению. Если мы определим join = run и unit = (<$>), мы получим две функции, которые составляют одну теоретическую основу для Monads, и если мы определим (>>=) = flip (use.pure) и return = unit, мы получим другую, которая использовалась в Haskell. Нет класса ApplicativeRun, потому что, если вы можете это сделать, вы можете сделать монаду, а подписи типов почти идентичны. Единственная причина, по которой мы имеем ArrowApply вместо повторного использования Monad, состоит в том, что типы не идентичны; ~> абстрагируется (обобщается) в интерфейс в ArrowApply, но приложение-приложение -> используется непосредственно в Monad. Это различие заключается в том, что делает программирование со Стрелками во многом отличным для программирования в монадах, несмотря на эквивалентность ArrowApply и Monad.

  • < кашель > Зачем нам когда-либо хотеть использовать Arrows/ArrowApply над Монадой?
     Хорошо, я признаю, что знал это, что вы имели в виду, но хотел поговорить об аппликативных функторах и был настолько увлечен, что забыл ответить!

    Возможные причины: Да, вы хотели бы использовать Arrow над Monad, если бы у вас было что-то, что невозможно сделать в монаду. Мотивационным примером, который привел нас к Arrows, в первую очередь, были синтаксические анализаторы - вы можете использовать Arrow для написания библиотеки парсера, которая делает статический анализ в комбинаторах, тем самым делая более эффективные парсеры. Предыдущие синтаксические анализаторы Monadic не могут этого сделать, потому что они представляют собой синтаксический анализатор как функцию, которая может делать произвольные вещи на вход, не записывая их статически, поэтому вы не можете анализировать их во время компиляции/комбинирования.

    Синтаксические причины: Нет, я лично не хотел бы использовать парсеры, основанные на стрелках, потому что мне не нравится нота стрелки proc/do - я нахожу ее еще хуже, чем монадические обозначения. Моя предпочтительная нотация для парсеров является аппликативной, и вы можете написать аппликативную парсерную библиотеку, которая делает эффективный статический анализ, который делает Arrow, но я свободно признаю, что библиотеки-парсеры, которые я обычно использую, не делают, возможно потому, что они хотят для обеспечения интерфейса Monadic.

    • Монада:

          parseTerm = do
               x <- parseSubterm
               o <- parseOperator
               y <- parseSubterm
               return $ Term x o y
      
    • Стрелка:

          parseTerm = proc _ -> do
               x <- parseSubterm -< ()
               o <- parseOperator -< ()
               y <- parseSubterm -< ()
               returnA -< Term x o y
      
    • Аппликация:

          parseTerm = Term <$> parseSubterm <*> parseOperator <*> parseSubterm
      

      Это выглядит как функциональное приложение с помощью $ несколько раз. Ммммм. Ухоженная. Очистить. Низкий синтаксис. Напоминает мне, почему я предпочитаю Haskell любому императивному языку программирования.

Почему приложение в ArrowApply создает Monad?

Там есть экземпляр Monad в разделе ArrowApply модуля Control.Arrow, и я отредактирую в (~>) вместо A за мою ясность мысли. (Я оставил Functor, потому что глупо определять Monad без Functor в любом случае - вы должны определить fmap f xs = xs >>= return . f.):

newtype ArrowMonad (~>) b = ArrowMonad (() ~> b)

instance Arrow (~>) => Functor (ArrowMonad (~>)) where
    fmap f (ArrowMonad m) = ArrowMonad $ m >>> arr f

instance ArrowApply (~>) => Monad (ArrowMonad (~>)) where
    return x = ArrowMonad (arr (\_ -> x))
    ArrowMonad m >>= f = ArrowMonad $
        m >>> arr (\x -> let ArrowMonad h = f x in (h, ())) >>> app

Что это делает? Во-первых, ArrowMonad является newtype вместо синонима типа, поэтому мы можем сделать экземпляр без всяких неприятных системных проблем типа, но не будем игнорировать это, чтобы пойти на концептуальную ясность над компилируемостью, заменив его так, как если бы он были type ArrowMonad (~>) b = () ~> b

instance Arrow (~>) => Functor (() ~>) where
    fmap f m = m >>> arr f

(используя раздел оператора несовместимого типа (()~>) как конструктор типа)

instance ArrowApply (~>) => Monad (() ~>) where
 -- return :: b -> (() ~> b)
    return x = arr (\_ -> x)
 -- (>>=) ::   ()~>a   ->    (a  ->  ()~>b )   ->   ()~>b 
    m >>= f = 
        m >>> arr (\x ->  (f x, ()) ) >>> app

ОК, это немного яснее, что происходит. Прежде всего обратите внимание, что соответствие между стрелками и монадами находится между Monad m => b -> m c и Arrow (~>) => b ~> c, но класс monad не включает B в декларации. Поэтому нам нужно указать фиктивное значение () в () ~> b, чтобы начать работу с нулевым входом и воспроизвести что-то типа m b.

  • Эквивалент fmap, в котором вы применяете функцию к вашему выводу, просто выводит результат, а затем запускает функцию в форме стрелки: fmap f m = m >>> arr f
  • Эквивалент return (который просто создает указанное значение x) - это просто запустить функцию const x в форме стрелки, следовательно return x = arr (\_ -> x).
  • В эквиваленте bind >>=, который запускает вычисление, затем использует вывод в качестве входного сигнала для функции f, которая затем может вычислить следующее вычисление для запуска: Сначала m >>> запустите первое вычисление m, затем arr (\x -> (f x, .... с выходом, примените функцию f, затем используйте эту стрелку в качестве входа в app, который ведет себя так, как если бы это была выведенная стрелка, действующая на входящий вход (), как обычно. Ухоженная!

Ответ 2

Точка зрения A немного нечетна - вообще говоря, абстракция не будет более сильной и более общей, чем какая-либо другая абстракция; эти два противоречат друг другу. Наличие "большей мощности" означает знание больше о структуре того, с чем вы работаете, что означает больше ограничений. С одной стороны, вы точно знаете, с каким типом вы работаете. Это чрезвычайно мощно; вы можете применить к нему любую действительную функцию. С другой стороны, он также не является общим в целом: код, написанный с этим предположением, применим только к этому типу! С другой стороны, вы ничего не знаете о своем типе (например, с переменной типа a). Это очень общее, применимое к каждому типу, но также и не очень мощное, поскольку у вас недостаточно информации, чтобы что-либо сделать вообще!

Примером большего корня в реальном коде является разница между Functor и Applicative. Здесь Functor является более общим - строго больше типов Functor чем Applicative s, так как каждый Applicative также является a Functor, но не наоборот. Однако, поскольку Applicative обладает большей структурой, он строго более мощный. С помощью Functor вы можете отображать только функции с одним аргументом над вашим типом; с помощью Applicative вы можете отображать функции любого числа аргументов. Опять же: один более общий, другой более мощный.

Итак, что это? Являются ли стрелы более мощными или более общими, чем монады? Это более сложный вопрос, чем сравнение функторов, аппликативных функторов и монад, потому что стрелки - совсем другой зверь. Они даже имеют другой вид: у монад и др. Есть вид * -> *, где стрелки имеют вид * -> * -> *. К счастью, оказывается, что мы можем идентифицировать стрелы с аппликативными функторами/монадами, поэтому мы можем реально ответить на этот вопрос: стрелки более общие, чем монады, и, следовательно, менее мощные. Учитывая любую монаду, мы можем построить из нее стрелу, но мы не можем построить монаду для каждой стрелки.

Основная идея заключается в следующем:

instance Monad m => Category (a -> m b) where
  id = return
  (f . g) x = g x >>= f

instance Monad m => Arrow (a -> m b) where
  arr f = return . f
  first f (x, y) = f x >>= \ x' -> return (x', y)

Однако, поскольку у нас есть экземпляр стрелки для a -> b, мы должны обернуть a -> m b в newtype в реальном коде. Этот newtype называется Klesli (из-за категорий Klesli).

Однако мы не можем пойти в другую сторону - нет никакой конструкции, чтобы получить Monad из любого Arrow. Это происходит потому, что вычисление Arrow не может изменить свою структуру на основе значений, протекающих через него, в то время как монад может. Единственный способ обойти это - добавить силу к вашей абстракции стрелки с помощью некоторой дополнительной примитивной функции; это именно то, что делает ArrowApply.

Оператор >>> для стрелок является обобщением функции . для функций и, следовательно, имеет те же общие ограничения. >>=, с другой стороны, больше похож на обобщение приложения функции. Обратите внимание на типы: для >>>, обе стороны - стрелки; для >>= первым аргументом является значение (m a), а вторая - функция. Более того, результатом >>> является другая стрелка, результатом которой является результат >>=. Поскольку стрелки имеют только >>>, но не эквивалентны значению >>=, вы не можете "применять" их к аргументам вообще - вы можете создавать конвейеры только конвейера. Фактическая функция apply/run должна быть конкретной для любой данной стрелки. Monads, с другой стороны, определены в терминах >>=, и поэтому приходят с некоторым понятием приложения по умолчанию.

ArrowApply просто расширяет стрелки с помощью app, что является общим понятием приложения. Представьте себе нормальное приложение:

apply :: (b -> c) -> b -> c
apply f x = f x

мы можем разобрать это:

apply :: ((b -> c), b) -> c

Способ обобщения функций стрелок в основном заключается в замене -> на переменную (a). Давайте сделаем это для apply, заменив оба вхождения -> инфикс a:

apply :: (b `a` c, b) `a` c

Мы все еще можем видеть ту же структуру, что и первая версия apply, только что uncurried и `a` вместо ->. Теперь, если мы просто избавимся от backticks и сделаем префикс a, получим подпись для app:

app :: a (a b c, b) c

Итак, мы видим, как ArrowApply просто добавляет некоторое понятие приложения в стрелки. Это параллель с >>=, который представляет собой понятие приложения для монад (или, в частности, функций формы a -> m b). Это достаточно дополнительная структура для построения монады из стрелки, поэтому ArrowApply изоморфна Monad.

Почему мы когда-нибудь захотим их использовать? Честно говоря, я не думаю, что мы это сделаем. Стрелки довольно завышены, поэтому придерживайтесь монад и аппликативных функторов.

Ответ 3

Monad - инструмент, который позволяет нам писать в императивном стиле (шаг за шагом).

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

Итак, монада для стрелок выглядит как линейная блок-схема.

http://www.soi.city.ac.uk/~ross/talks/fop.pdf

http://www.haskell.org/arrows/syntax.html