Парсек: Аппликатив против монад

Я только начинаю с Parsec (с небольшим опытом работы с Haskell), и меня немного смущает использование монад или аппликативов. Общее ощущение, которое я испытал после прочтения "Real World Haskell", "Write You a Haskell", и вопрос здесь в том, что предпочтительны аппликативные выражения, но на самом деле я понятия не имею.

Итак, мои вопросы:

Какой подход предпочтительнее?Можно ли смешивать монады и аппликативы (используйте их, когда они более полезны, чем другие)Если последний ответ - да, должен ли я это сделать?
 ErikR01 авг. 2016 г., 22:58
Моя рекомендация состоит в том, чтобы узнать, как использовать parsec, используя монадическую do-нотацию. Затем, когда вы узнаете о аппликативах, вы можете начать использовать их вместо своего монадического кода. Аппликативные средства строго менее мощны, чем монады, поэтому могут быть некоторые вещи, которые вы не можете делать только с аппликативными. Теперь меньше различий, когда вы используете do-notation для написания аппликативных выражений с прагмой ApplicativeDo.

Ответы на вопрос(3)

чтобы комментировать увы) это стоит отметитьjoin $ fM <*> ... <*> ... <*> ... как образец, а также. Он точно так же объединяет семью "bind1, bind2, bind3 ..."<$> а также<*> получите "fmap1, fmap2, fmap3".

Как стилистическая вещь, когда вы достаточно привыкли к комбинаторам, можно использоватьdo так же, как вы бы использовалиlet: как способ выделить, когда вы хотите назвать что-то. Например, я хочу чаще называть вещи в парсерах, потому что это, вероятно, соответствует именам в спецификации!

 dfeuer03 авг. 2016 г., 09:21
Не могли бы вы расширить это, чтобы уточнить? Первый абзац довольно расплывчатый; второй мог бы использовать несколько примеров.

стоит обратить внимание на ключевое семантическое различие междуApplicative а такжеMonad, чтобы определить, когда каждый уместен. Сравнить типы:

(<*>) :: m (s -> t) -> m s -> m t
(>>=) :: m s -> (s -> m t) -> m t

Развернуть<*>, вы выбираете два вычисления, одно из функции, другое из аргумента, затем их значения объединяются приложением. Развернуть>>=Вы выбираете одно вычисление и объясняете, как вы будете использовать полученные значения для выбора следующего вычисления. Это разница между «пакетным режимом» и «интерактивной» операцией.

Когда дело доходит до разбора,Applicative (продлен с неудачей и выбором, чтобы датьAlternative) фиксируетконтекстно-независимый аспекты вашей грамматики. Вам понадобится дополнительная сила, котораяMonad дает вам, только если вам нужно проверить дерево разбора из части вашего ввода, чтобы решить, какую грамматику вы должны использовать для другой части вашего ввода. Например, вы можете прочитать дескриптор формата, а затем ввод в этом формате. Минимизация использования вами дополнительных мощностей монад говорит о том, какие значения-зависимости необходимы.

Переходя от разбора к параллелизму, эта идея использования>>= Только для существенных зависимостей от ценностей вы получаете ясность относительно возможностей распределения нагрузки. Когда два вычисления объединяются с<*>Никому не нужно ждать другого. Применимо, когда ты можешь, но монадичен, когда ты должен - формула скорости. ТочкаApplicativeDo заключается в том, чтобы автоматизировать анализ зависимостей кода, который был написан в монадическом стиле и, таким образом, случайно переназначен.

Ваш вопрос также относится к стилю кодирования, о котором мнения могут свободно различаться. Но позвольте мне рассказать вам историю. Я пришел в Haskell из Standard ML, где я привык писать программы в прямом стиле, даже если они делали непослушные вещи, такие как исключения исключения или ссылки на мутированные строки. Что я делал в ОД? Работа над реализацией теории сверхчистых типов (которая не может быть названа по юридическим причинам). При работев В этой теории типов я не мог писать программы прямого стиля, в которых использовались исключения, но я разработал аппликативные комбинаторы как способ максимально приблизиться к прямому стилю.

Когда я переехал в Хаскелл, я был в ужасе, обнаружив, в какой степени люди, кажется, думают, что программирование в псевдо-императивной нотации было всего лишь наказанием за малейшую семантическую нечистоту (кроме, конечно, не прекращения). Я принял аппликативные комбинаторы как выбор стиля (и даже приблизился к прямому стилю с помощью «идиоматических скобок») задолго до того, как я понял семантическое различие, то есть то, что они представляли полезное ослабление интерфейса монады. Мне просто не понравилось (и до сих пор не нравится), что do-notation требует фрагментации структуры выражения и произвольного именования вещей.

То есть те же самые вещи, которые делают функциональный код более компактным и читаемым, чем императивный код, также делают аппликативный стиль более компактным и читаемым, чем do-notation. я ценю этоApplicativeDo это отличный способ сделать более аппликативным (а в некоторых случаях это означает,Быстрее) программы, написанные в монадическом стиле, на которые у вас нет времени на рефакторинг. Но в противном случае, я бы сказал, что аппликативное «когда можешь, но монадный - когда ты должен» - это также лучший способ увидеть, что происходит.

 Benjamin Hodgson03 авг. 2016 г., 02:32
Отражает ли ваше предпочтение программированию в аппликативном стиле (под которым я подразумеваю то, что вы называете «прямым стилем», в отличие от «Applicative стиль ") распространяться на предпочтение>>= надdo?
 Fraser03 авг. 2016 г., 20:16
@RobStewartConcurrently отasync параллельно.
 Rob Stewart03 авг. 2016 г., 16:22
Пример того, где я видел распараллеливание аппликативов, приведен в документе Haxl ICFP'14, например,length <$> intersect’ (friendsOf x) (friendsOf y) community.haskell.org/~simonmar/papers/haxl-icfp14.pdf , Кроме Haxl, есть ли другие примеры экземпляров Applicative, в которые встроен параллелизм их реализаций?
 Sam Elliott02 авг. 2016 г., 19:20
Также стоит отметить, что парсеры, которые вы можете сделать с<*> строго менее выразительны, чем те, которые вы можете сделать с>>= - хотя дополнительная выразительность не всегда нужна. Этот ответ намекает на различия далееstackoverflow.com/a/7863380/261393
 Cactus03 авг. 2016 г., 06:24
@BenjaminHodgson: я думал, что «прямой стиль», в крайнем случае, будет тем, где вы используете синтаксис приложения функции (зарезервировано для-> функциональное пространство в Haskell). Для примера реализации в современной обстановке,в Идрисе ты можешь написать[| f x y + z |] означать(+) <$> (f <$> x <*> y) <*> z
 pigworker03 авг. 2016 г., 20:49
@RobStewart Работа Саймона М - это то, что я имел в виду, когда писал этот ответ. Между тем бесконечные потоки образуют монаду, гдеrepeat являетсяreturn а такжеjoin занимает диагональ бесконечной квадратной матрицы. Конечно, если вам нужна только диагональ, глупо вычислять даже матрицу блоков:>>= генерирует как минимум треугольник. В этом смысле разумным способом объединения потоков являетсяпроноситься, что является именно аппликативным поведением. Это может быть не совсем параллелизм, но это еще один пример, где реализация по умолчанию<*> от>>= неэффективно чрезмерно последовательным.
Решение Вопроса

начните с того, что для вас наиболее разумно. После рассмотрим следующее.

Это хорошая практика для использованияApplicative (или дажеFunctor) когда возможно. , компилятору, подобному GHC, проще оптимизировать эти экземпляры, поскольку они могут быть проще, чемMonad, Я думаю, что совет сообщества в целомAMP было сделать как можно более общие ваши ограничения. Я бы порекомендовал использовать расширение GHCApplicativeDo так как вы можете равномерно использоватьdo запись в то время как только получениеApplicative ограничение, когда это все, что нужно.

ПосколькуParsecT Тип парсера является экземпляром обоихApplicative а такжеMonadВы можете смешивать и сочетать два. Есть ситуации, когда это становится более читабельным - все зависит от ситуации.

Также рассмотрите возможность использованияmegaparsec. megaparsec более активно поддерживается только в целом чище, более позднийparsec.

РЕДАКТИРОВАТЬ

Две вещи, которые, перечитывая мой ответ и комментарии, я действительно не очень хорошо разъяснил:

главное преимущество использованияApplicative является то, что для многих типов он допускает гораздо более эффективные реализации (например,(<*>) более производительный, чемap).

Если вы просто хотите написать что-то вроде(+) <$> parseNumber <*> parseNumberнет необходимости заходить вApplicativeDo - это было бы просто более многословно. Я бы использовалApplicativeDo только когда вы начинаете писать очень длинные или вложенные аппликативные выражения.

 dfeuer02 авг. 2016 г., 18:17
А может и не быть! Я думаю, что большинство традиционных библиотек синтаксических анализаторов основаны на типах синтаксических анализаторов, которыене поддержка особенно быстрых аппликативных операций. Я не знаю, есть ли такие, которые поддерживают монадический интерфейс и оптимизируют аппликативный.
 dfeuer02 авг. 2016 г., 17:22
Это не в основном окомпилятор оптимизация; это гораздо больше обиблиотека оптимизаций. Некоторые типы поддерживают сверхэффективные<$, fmap, *>, <*и / или<*> на основе их структур; другие нет.Data.Sequence получает значительное увеличение скорости за каждый шаг вниз (хотя<* в настоящее время отсутствует; Я должен это исправить). Списки, с другой стороны, получают только крошечные (кроме особого случая map / coerce).
 José Miguel01 авг. 2016 г., 23:14
Спасибо! Я проверю мегапарсек, кажется хорошим (поддержка Юникода и отступов выглядит великолепно!)
 Alec02 авг. 2016 г., 18:04
@dfeuer Хммм, вот что я имел в виду под «этими примерами, так как они могут быть проще, чемMonad«Но если вы комментируете, то мой ответ не очевиден. :) Я не хотел переоценивать это здесь, потому что когда я смотрел на(<*>) заParsecTкажется, это не слишком эффективно по сравнению с(>>=)...

Ваш ответ на вопрос