какова цель стрел?

63

Я изучаю программирование на языке Haskell и пытаюсь понять концепции, сначала поняв, зачем они мне нужны.

Я хотел бы знать цель стрелок в функциональных языках программирования. Какую проблему они решают? Я проверил http://en.wikibooks.org/wiki/Haskell/Understanding_arrows и http://www.cse.chalmers.se/~rjmh/afp-arrows.pdf . Все, что я понимаю, - это то, что они используются для описания графиков для вычислений, и что они позволяют проще кодировать произвольные стили.

В статье предполагается, что стиль без точек, как правило, легче понять и написать. Это кажется мне весьма субъективным. В другой статье ( http://en.wikibooks.org/wiki/Haskell/StephensArrowTutorial#Hangman:_Main_program ) реализована игра палача, но я не вижу, как стрелки делают эту реализацию естественной.

Я мог найти много работ, описывающих концепцию, но ничего о мотивации.

Чего мне не хватает?

Саймон Бергот
источник

Ответы:

43

Я понимаю, что опаздываю на вечеринку, но у вас здесь есть два теоретических ответа, и я хотел предоставить практическую альтернативу пережевыванию. Я подхожу к этому как относительный новичок Хаскелла, который, тем не менее, недавно подвергся насильственному маршу по теме «Стрелы» для проекта, над которым я сейчас работаю.

Во-первых, вы можете продуктивно решать большинство проблем в Хаскеле, не доходя до Стрелок. Некоторые известные Haskellers искренне не любят и не используют их (см. Здесь , здесь , и здесь для получения дополнительной информации об этом). Поэтому, если вы говорите себе «Эй, мне это не нужно», поймите, что вы действительно можете быть правы.

Что меня больше всего огорчило в Arrows, когда я впервые узнал о них, так это то, как учебные материалы по этой теме неизбежно достигли аналогии со схемой. Если вы посмотрите на код стрелки - по крайней мере, на подслащенный вариант - он больше не похож на язык аппаратного определения. Ваши входы располагаются справа, ваши выходы слева, и если вы не подключите их все правильно, они просто не сработают. Я подумал про себя: правда? Это то, где мы оказались? Мы создали язык настолько высокого уровня, что он снова состоит из медных проводов и припоя?

Насколько я смог определить, правильный ответ на этот вопрос таков : на самом деле, да. В настоящий момент убийственный сценарий использования Arrows - это FRP (вспомним Yampa, игры, музыку и реактивные системы в целом). Проблема, с которой сталкивается FRP, в значительной степени та же проблема, с которой сталкиваются все другие системы синхронного обмена сообщениями: как связать непрерывный поток входных данных в непрерывный поток выходных данных без потери соответствующей информации или возникающих утечек. Вы можете смоделировать потоки в виде списков - несколько последних систем FRP используют этот подход - но когда у вас много входных списков, управлять ими практически невозможно. Вам нужно оградить себя от течения.

Что позволяет стрелкам в системах FRP, так это составление функций в сеть, в то же время полностью абстрагируя любые ссылки на базовые значения, передаваемые этими функциями. Если вы новичок в FP, это может сначала сбить вас с толку, а затем и ошеломить, когда вы осознаете последствия этого. Вы только недавно освоили идею о том, что функции могут быть абстрагированы, и как понимать список как имеющий [(*), (+), (-)]тип [(a -> a -> a)]. С помощью стрелок вы можете продвинуть абстракцию еще на один уровень.

Эта дополнительная способность к абстракции несет в себе свои опасности. С одной стороны, это может подтолкнуть GHC в угловые случаи, когда он не знает, что делать с вашими предположениями типа. Вы должны быть готовы думать на уровне типов - это отличная возможность узнать о типах, RankNTypes и других подобных темах.

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

ПРИМЕЧАНИЕ: как я уже упоминал выше, я относительный нуб. Если я обнародовал какие-либо заблуждения, пожалуйста, поправьте меня.

rtperson
источник
2
Я счастлив, что еще ничего не принял. Спасибо за предоставленный ответ. Это больше ориентировано на пользователей. Примеры отличные. Субъективные части четко определены и сбалансированы. Я надеюсь, что люди, которые проголосовали за этот вопрос, вернутся и увидят это.
Саймон Бергот
Хотя стрелки - определенно неправильный инструмент для вашего связанного решения, я чувствую, что должен упомянуть, что это 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 (++)).
cemper93
30

Это своего рода «мягкий» ответ, и я не уверен, что какая-либо ссылка на самом деле утверждает это таким образом, но вот как я начал думать о стрелках:

Тип стрелки 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поведение во многом похоже на функцию, то есть общая структура - это то, что называется «стрелкой».

Lambdageek
источник
«Стрелка защищает вас от использования bи создания cисключений, если вы участвуете в различных комбинаторах стрелок или используете примитивные операции конкретного экземпляра стрелки». С извинениями за ответ на этот древний ответ: это предложение заставило меня задуматься о линейных типах, то есть о том, что ресурсы нельзя клонировать или исчезать. Как вы думаете, может быть какая-то связь?
glaebhoerl
14

Мне нравится думать о стрелках, таких как монады и функторы, как позволяющих программисту создавать экзотические композиции функций.

Без Монад или Стрелок (и Функторов) состав функций в функциональном языке ограничен применением одной функции к результату другой функции. С помощью монад и функторов вы можете определить две функции, а затем написать отдельный повторно используемый код, который указывает, как эти функции в контексте конкретной монады взаимодействуют друг с другом и с данными, которые передаются в них. Этот код находится внутри кода связывания монады. Таким образом, монада - это одно-единственное представление, просто контейнер для повторно используемого кода связывания. Функции составляются по-разному в контексте одной монады из другой монады.

Простым примером является монада Maybe, где в функции связывания есть код, такой, что если функция A состоит из функции B внутри монады Maybe, а B создает Nothing, то код связывания гарантирует, что состав две функции выводят Nothing, не удосуживаясь применить A к значению Nothing, исходящему из B. Если бы не было монады, программист должен был бы написать код в A, чтобы проверить наличие ввода Nothing.

Монады также означают, что программисту не нужно явно вводить параметры, которые требуются каждой функции, в исходный код - функция bind обрабатывает передачу параметров. Таким образом, используя монады, исходный код может начать больше походить на статическую цепочку имен функций, а не выглядеть так, как будто функция A «вызывает» функцию B с параметрами C и D - код начинает ощущаться больше как электронная схема, чем движущаяся машина - более функциональная, чем императивная.

Стрелки также связывают функции вместе с функцией связывания, обеспечивая многоразовую функциональность и скрывая параметры. Но сами стрелки могут быть соединены и составлены, а также могут при желании перенаправлять данные в другие стрелки. Теперь вы можете применить данные к двум путям со стрелками, которые «делают разные вещи» с данными, и заново собрать результат. Или вы можете выбрать, в какую ветвь стрелок передавать данные, в зависимости от некоторого значения в данных. Полученный код еще больше похож на электронную схему с переключателями, задержками, интеграцией и т. Д. Программа выглядит очень статично, и вы не должны видеть, как происходит много манипуляций с данными. Все меньше и меньше нужно думать о параметрах, и меньше нужно думать о том, какие значения могут принимать или не принимать параметры.

Написание программы Arrowized в основном включает в себя выбор готовых стрелок, таких как сплиттеры, переключатели, задержки и интеграторы, подъем функций в эти стрелки и соединение стрелок вместе для формирования больших стрелок. В Arrowized Functional Reactive Programming стрелки образуют цикл, в котором входные данные из мира объединяются с выходными данными из последней итерации программы, так что выходные данные реагируют на входные данные реального мира.

Одна из ценностей реального мира - время. В Yampa Стрелка функции сигнала невидимо пропускает параметр времени через компьютерную программу - вы никогда не получите доступ к значению времени, но если вы подключите стрелку интегратора в программу, она выведет значения, интегрированные с течением времени, которые затем можно использовать для передачи в другие стрелки

Роберт Онслоу
источник
но это звучит как аппликативный функтор (некоторая оболочка вокруг функции, которая в некоторых конкретных контекстах предоставляет вспомогательные функции для повторного использования уже существующих функций для упакованных типов). Мне определенно нужно больше читать, чтобы понять, но, может быть, вы можете помочь, указав, что мне не хватает
Белун
3

Просто дополнение к другим ответам: Лично мне очень помогает понять, что такое понятие (математически) и как оно связано с другими понятиями, которые я знаю.

В случае стрелок я нашел следующую статью полезной - она ​​сравнивает монады, аппликативные функторы (идиомы) и стрелки: идиомы не обращают внимания, стрелки дотошны, монады неразборчивы от Сэма Линдли, Филипа Уодлера и Джереми Яллопа.

Также я считаю, что никто не упомянул эту ссылку, которая может дать вам некоторые идеи и литературу по этому вопросу.

Петр Пудлак
источник