Я понимаю, что опаздываю на вечеринку, но у вас здесь есть два теоретических ответа, и я хотел предоставить практическую альтернативу пережевыванию. Я подхожу к этому как относительный новичок Хаскелла, который, тем не менее, недавно подвергся насильственному маршу по теме «Стрелы» для проекта, над которым я сейчас работаю.
Во-первых, вы можете продуктивно решать большинство проблем в Хаскеле, не доходя до Стрелок. Некоторые известные Haskellers искренне не любят и не используют их (см. Здесь , здесь , и здесь для получения дополнительной информации об этом). Поэтому, если вы говорите себе «Эй, мне это не нужно», поймите, что вы действительно можете быть правы.
Что меня больше всего огорчило в Arrows, когда я впервые узнал о них, так это то, как учебные материалы по этой теме неизбежно достигли аналогии со схемой. Если вы посмотрите на код стрелки - по крайней мере, на подслащенный вариант - он больше не похож на язык аппаратного определения. Ваши входы располагаются справа, ваши выходы слева, и если вы не подключите их все правильно, они просто не сработают. Я подумал про себя: правда? Это то, где мы оказались? Мы создали язык настолько высокого уровня, что он снова состоит из медных проводов и припоя?
Насколько я смог определить, правильный ответ на этот вопрос таков : на самом деле, да. В настоящий момент убийственный сценарий использования Arrows - это FRP (вспомним Yampa, игры, музыку и реактивные системы в целом). Проблема, с которой сталкивается FRP, в значительной степени та же проблема, с которой сталкиваются все другие системы синхронного обмена сообщениями: как связать непрерывный поток входных данных в непрерывный поток выходных данных без потери соответствующей информации или возникающих утечек. Вы можете смоделировать потоки в виде списков - несколько последних систем FRP используют этот подход - но когда у вас много входных списков, управлять ими практически невозможно. Вам нужно оградить себя от течения.
Что позволяет стрелкам в системах FRP, так это составление функций в сеть, в то же время полностью абстрагируя любые ссылки на базовые значения, передаваемые этими функциями. Если вы новичок в FP, это может сначала сбить вас с толку, а затем и ошеломить, когда вы осознаете последствия этого. Вы только недавно освоили идею о том, что функции могут быть абстрагированы, и как понимать список как имеющий [(*), (+), (-)]
тип [(a -> a -> a)]
. С помощью стрелок вы можете продвинуть абстракцию еще на один уровень.
Эта дополнительная способность к абстракции несет в себе свои опасности. С одной стороны, это может подтолкнуть GHC в угловые случаи, когда он не знает, что делать с вашими предположениями типа. Вы должны быть готовы думать на уровне типов - это отличная возможность узнать о типах, RankNTypes и других подобных темах.
Есть также ряд примеров того, что я бы назвал «Тупыми трюками со стрелками», когда кодировщик тянется к какому-то комбинатору стрелок только потому, что он или она хочет показать аккуратный трюк с кортежами. (Вот мой собственный тривиальный вклад в безумие .) Не стесняйтесь игнорировать такие хот-дог, когда вы сталкиваетесь с этим в дикой природе.
ПРИМЕЧАНИЕ: как я уже упоминал выше, я относительный нуб. Если я обнародовал какие-либо заблуждения, пожалуйста, поправьте меня.
removeAt' n = arr(\ xs -> (xs,xs)) >>> arr (take (n-1)) *** arr (drop n) >>> arr (uncurry (++)) >>> returnA
можно записать более кратко и четкоremoveAt' n = (arr (take $ n-1) &&& arr (drop n)) >>> (arr $ uncurry (++))
.Это своего рода «мягкий» ответ, и я не уверен, что какая-либо ссылка на самом деле утверждает это таким образом, но вот как я начал думать о стрелках:
Тип стрелки
A b c
- это, в основном, функция,b -> c
но с большей структурой, аналогично тому, как монадическое значениеM a
имеет большую структуру, чем обычное староеa
.Теперь, какой будет эта дополнительная структура, зависит от конкретного экземпляра стрелки, о котором вы говорите. Так же, как с монадами,
IO a
иMaybe a
каждая имеет разную дополнительную структуру.То, что вы получаете с монадами, это неспособность перейти от одного
M a
к другомуa
. Теперь это может показаться ограничением, но на самом деле это особенность: система типов защищает вас от превращения монадического значения в обычное старое значение. Вы можете использовать значение, только участвуя в монаде через>>=
или примитивные операции конкретного экземпляра монады.Аналогично, вы получаете
A b c
неспособность создать новую b-потребляющую c-производящую «функцию». Стрелка защищает вас от использованияb
и созданияc
исключений, участвуя в различных комбинаторах стрелок или используя примитивные операции конкретного экземпляра стрелки.Например, функции сигнала в Yampa являются приблизительными
(Time -> a) -> (Time -> b)
, но, кроме того, они должны подчиняться определенному ограничению причинности : выход во времениt
определяется прошлыми значениями входного сигнала: вы не можете смотреть в будущее. Поэтому вместо программирования(Time -> a) -> (Time -> b)
вы программируете,SF a b
и вы строите свои сигнальные функции из примитивов. Так получилось, что посколькуSF a b
поведение во многом похоже на функцию, то есть общая структура - это то, что называется «стрелкой».источник
b
и созданияc
исключений, если вы участвуете в различных комбинаторах стрелок или используете примитивные операции конкретного экземпляра стрелки». С извинениями за ответ на этот древний ответ: это предложение заставило меня задуматься о линейных типах, то есть о том, что ресурсы нельзя клонировать или исчезать. Как вы думаете, может быть какая-то связь?Мне нравится думать о стрелках, таких как монады и функторы, как позволяющих программисту создавать экзотические композиции функций.
Без Монад или Стрелок (и Функторов) состав функций в функциональном языке ограничен применением одной функции к результату другой функции. С помощью монад и функторов вы можете определить две функции, а затем написать отдельный повторно используемый код, который указывает, как эти функции в контексте конкретной монады взаимодействуют друг с другом и с данными, которые передаются в них. Этот код находится внутри кода связывания монады. Таким образом, монада - это одно-единственное представление, просто контейнер для повторно используемого кода связывания. Функции составляются по-разному в контексте одной монады из другой монады.
Простым примером является монада Maybe, где в функции связывания есть код, такой, что если функция A состоит из функции B внутри монады Maybe, а B создает Nothing, то код связывания гарантирует, что состав две функции выводят Nothing, не удосуживаясь применить A к значению Nothing, исходящему из B. Если бы не было монады, программист должен был бы написать код в A, чтобы проверить наличие ввода Nothing.
Монады также означают, что программисту не нужно явно вводить параметры, которые требуются каждой функции, в исходный код - функция bind обрабатывает передачу параметров. Таким образом, используя монады, исходный код может начать больше походить на статическую цепочку имен функций, а не выглядеть так, как будто функция A «вызывает» функцию B с параметрами C и D - код начинает ощущаться больше как электронная схема, чем движущаяся машина - более функциональная, чем императивная.
Стрелки также связывают функции вместе с функцией связывания, обеспечивая многоразовую функциональность и скрывая параметры. Но сами стрелки могут быть соединены и составлены, а также могут при желании перенаправлять данные в другие стрелки. Теперь вы можете применить данные к двум путям со стрелками, которые «делают разные вещи» с данными, и заново собрать результат. Или вы можете выбрать, в какую ветвь стрелок передавать данные, в зависимости от некоторого значения в данных. Полученный код еще больше похож на электронную схему с переключателями, задержками, интеграцией и т. Д. Программа выглядит очень статично, и вы не должны видеть, как происходит много манипуляций с данными. Все меньше и меньше нужно думать о параметрах, и меньше нужно думать о том, какие значения могут принимать или не принимать параметры.
Написание программы Arrowized в основном включает в себя выбор готовых стрелок, таких как сплиттеры, переключатели, задержки и интеграторы, подъем функций в эти стрелки и соединение стрелок вместе для формирования больших стрелок. В Arrowized Functional Reactive Programming стрелки образуют цикл, в котором входные данные из мира объединяются с выходными данными из последней итерации программы, так что выходные данные реагируют на входные данные реального мира.
Одна из ценностей реального мира - время. В Yampa Стрелка функции сигнала невидимо пропускает параметр времени через компьютерную программу - вы никогда не получите доступ к значению времени, но если вы подключите стрелку интегратора в программу, она выведет значения, интегрированные с течением времени, которые затем можно использовать для передачи в другие стрелки
источник
Просто дополнение к другим ответам: Лично мне очень помогает понять, что такое понятие (математически) и как оно связано с другими понятиями, которые я знаю.
В случае стрелок я нашел следующую статью полезной - она сравнивает монады, аппликативные функторы (идиомы) и стрелки: идиомы не обращают внимания, стрелки дотошны, монады неразборчивы от Сэма Линдли, Филипа Уодлера и Джереми Яллопа.
Также я считаю, что никто не упомянул эту ссылку, которая может дать вам некоторые идеи и литературу по этому вопросу.
источник