Ответ 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
, который ведет себя так, как если бы это была выведенная стрелка, действующая на входящий вход()
, как обычно. Ухоженная!