Может кто-нибудь объяснить, как я неправильно понимаю, как работают стрелки, и как их можно использовать? Есть ли в Arrows фундаментальная философия, по которой я скучаю?
У меня сложилось впечатление, что вы относитесь к этому Arrow
, как если бы вы Monad
. Я не знаю, считается ли это «фундаментальной философией», но между ними есть существенная разница, несмотря на то, как часто они совпадают. В некотором смысле ключевой вещью, которая определяет Monad
, является функция join
; как свернуть вложенную структуру в один слой. Они полезны из-за того, что позволяет join
: вы можете создавать новые монадические слои в рекурсивной функции, изменять структуру Functor
на основе ее содержимого и так далее. Но это не о Monad
с, поэтому мы оставим это на этом.
Суть Arrow
, с другой стороны, является обобщенная версия функции . Класс типа Category
определяет обобщенные версии композиции функций и функции идентификации, в то время как класс типа Arrow
определяет, как поднять обычную функцию до Arrow
и как работать с Arrow
s, которые принимают несколько аргументов ( в виде кортежей - Arrows
не обязательно может быть карри!).
При базовом объединении Arrow
s, как в вашей первой countExample
функции, все, что вы действительно делаете, это что-то вроде сложной композиции функции . Посмотрите на свое определение (.)
- вы берете две функции с состоянием и соединяете их в одну функцию с состоянием, при этом поведение изменения состояния обрабатывается автоматически.
Итак, главная проблема с вашим countExample
в том, что он даже упоминает count'
и тому подобное. Все это делается за кулисами, точно так же, как вам не нужно явно передавать параметр состояния при использовании нотации do
в монаде State
.
Теперь, поскольку нотация proc
позволяет вам просто создавать большие составные Arrow
с, чтобы на самом деле использовать вашу функцию с состоянием, вам нужно работать вне синтаксиса Arrow
, как и вы нужно runState
или что-то подобное для фактического запуска вычисления в монаде State
. Ваш второй countExample
такой же, но слишком специализированный. В общем случае ваша функция с состоянием отображает stream входов на stream выходов, что делает его конечным преобразователем состояния , так что runStatefulFunction
, вероятно, будет возьмите ленивый список входных значений и преобразуйте их в ленивый список выходных значений, используя правый сгиб с unSF
для подачи каждого к преобразователю по очереди.
Если вы хотите увидеть пример, пакет arrows
включает Arrow
трансформатор Automaton
, который определяет что-то, почти идентичное вашему StatefulFunction
, за исключением произвольного Arrow
вместо простой функции, которую вы использовали.
О, и кратко рассмотрим взаимосвязь между Arrow
с и Monad
с:
Обычные Arrows
- это только функции первого порядка. Как я уже говорил, их не всегда можно каррировать, а также они не всегда могут быть «применены» в том же смысле, в котором функция ($)
применяет функции. Если вы действительно хотите более высокий порядок Arrows
, класс типов ArrowApply
определяет приложение Arrow
. Это добавляет значительную мощность к Arrow
и, среди прочего, позволяет использовать ту же функцию "свертывания вложенной структуры", которую обеспечивает Monad
, что позволяет вообще определять экземпляр Monad
для любого ArrowApply
экземпляра.
В другом направлении, поскольку Monad
s позволяют комбинировать функции, которые создают новую монадическую структуру, для любого Monad
m
можно говорить о «стрелке Клейсли», которая является функцией типа a -> m b
. Стрелкам Клейсли для Monad
можно дать экземпляр Arrow
довольно очевидным способом.
За исключением ArrowApply
и стрелок Клейсли, между классами типов нет особой интересной связи.