Что такое (функциональное) реактивное программирование?

Многие из ответов, как мне кажется, игнорируют указанные требования:

  • Результатом должен быть массив байтов
  • Он должен быть как можно более эффективным

Эти два вместе исключают последовательность байтов LINQ - что-либо с yield будет делать невозможным получение окончательного размера без итерации по всей последовательности.

Если это не требования real , конечно, LINQ может быть идеальным решением (или реализацией IList<T>). Тем не менее, я предполагаю, что Superdumbell знает, чего он хочет.

(EDIT: У меня была другая мысль: существует большая смысловая разница между копированием массивов и ленивым чтением. происходит, если вы измените данные в одном из массивов «источник» после вызова метода Combine (или любого другого), но перед использованием результата - с ленивой оценкой это изменение будет видимым. С немедленной копией он не будет Различные ситуации потребуют различного поведения - просто что-то, о чем нужно знать.)

Вот мои предложенные методы, которые очень похожи на те, что содержатся в некоторых других ответах, конечно:)

public static byte[] Combine(byte[] first, byte[] second)
{
    byte[] ret = new byte[first.Length + second.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    return ret;
}

public static byte[] Combine(byte[] first, byte[] second, byte[] third)
{
    byte[] ret = new byte[first.Length + second.Length + third.Length];
    Buffer.BlockCopy(first, 0, ret, 0, first.Length);
    Buffer.BlockCopy(second, 0, ret, first.Length, second.Length);
    Buffer.BlockCopy(third, 0, ret, first.Length + second.Length,
                     third.Length);
    return ret;
}

public static byte[] Combine(params byte[][] arrays)
{
    byte[] ret = new byte[arrays.Sum(x => x.Length)];
    int offset = 0;
    foreach (byte[] data in arrays)
    {
        Buffer.BlockCopy(data, 0, ret, offset, data.Length);
        offset += data.Length;
    }
    return ret;
}

Конечно, версия «params» требует создания массива массивов байтов, что приводит к дополнительной неэффективности.

1149
задан BanksySan 9 March 2017 в 17:37
поделиться

6 ответов

Если вы хотите почувствовать FRP, вы можете начать со старого учебника Fran 1998 года, в котором есть анимированные иллюстрации. Что касается статей, начните с Functional Reactive Animation , а затем следуйте ссылкам на ссылку публикации на моей домашней странице и ссылку FRP на вики Haskell . 1266] Лично мне нравится думать о том, что означает FRP , прежде чем рассматривать, как это может быть реализовано. (Код без спецификации - это ответ без вопросов и, следовательно, «даже не ошибочный».) Поэтому я не описываю FRP в терминах представления / реализации, как Томас К. в другом ответе (графы, узлы, ребра, запуск, выполнение и т. Д.). Существует много возможных стилей реализации, но ни одна реализация не говорит, что такое FRP .

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

FRP также необычен тем, что он является параллельным, не вступая в противоречие с теоретическими и прагматическими «крысиное гнездо», отравляющее императивный параллелизм. Семантически параллелизм FRP является мелкозернистым , определенным и непрерывным . (Я говорю о смысле, а не о реализации. Реализация может включать, а может и не включать параллелизм или параллелизм.) Семантическая определенность очень важна для рассуждений, как строгих, так и неформальных. В то время как параллелизм чрезвычайно усложняет императивное программирование (из-за недетерминированного чередования), он не требует усилий в FRP.

Итак, что такое FRP? Вы могли бы это сами придумать. Начните с этих идей:

  • Динамические / развивающиеся ценности (т. Е. Ценности «с течением времени») сами по себе являются ценностями первого класса. Вы можете определять их и комбинировать, передавать в функции и из них. Я назвал эти вещи «поведением».

  • Поведение строится из нескольких примитивов, таких как постоянное (статическое) поведение и время (например, часы), а затем из последовательной и параллельной комбинации. n поведения комбинируются путем применения n-арной функции (на статических значениях) «по точкам», т. Е. Непрерывно во времени.

  • Для учета дискретных явлений используйте другой тип (семейство) «событий», каждое из которых имеет поток (конечный или бесконечный) событий. Каждому событию соответствует время и значение.

  • Чтобы придумать композиционный словарь, из которого можно построить все поведения и события, поиграйте с некоторыми примерами. Продолжайте деконструировать на более общие / простые части.

  • Чтобы вы знали, что стоите на твердой почве, дайте всей модели композиционную основу, используя технику денотационной семантики, что просто означает, что (а) каждый тип имеет соответствующий простой и точный математический тип «значений», и (б) каждый примитив и оператор имеет простое и точное значение в зависимости от значений составляющих. Никогда, никогда не добавляйте соображения реализации в свой процесс исследования. Если это описание кажется вам тарабарщиной, обратитесь к (a) Денотационный дизайн с морфизмами классов типов , (b) Двухтактное функциональное реактивное программирование (игнорирование битов реализации) и (c ) денотационная семантика викиучебники Haskell . Помните, что денотационная семантика состоит из двух частей, от двух ее основателей Кристофера Стрейчи и Даны Скотт: более легкая и полезная часть Стрейчи и более сложная и менее полезная (для разработки программного обеспечения) часть Скотта.

Если вы будете придерживаться этих принципов, я ожидайте, что вы получите что-то более или менее в духе FRP.

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

Было довольно сложно реализовать эту модель правильно и эффективно, но это уже другая история.

931
ответ дан 19 December 2019 в 20:16
поделиться

Статья Простая эффективная функциональная реактивность Конала Эллиота ( прямая PDF , 233 КБ) - довольно хорошее введение. Соответствующая библиотека также работает.

Эта статья теперь заменена другой статьей, Двухтактное функциональное реактивное программирование ( прямой PDF , 286 КБ).

29
ответ дан 19 December 2019 в 20:16
поделиться

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

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

Основная идея реактивного программирования заключается в том, что существуют определенные типы данных, которые представляют значение «с течением времени». Вычисления, которые включают эти изменяющиеся во времени значения, сами будут иметь значения, которые изменяются во времени.

Например, вы можете представить координаты мыши как пару целочисленных значений во времени. Допустим, у нас есть что-то вроде (это псевдокод):

x = <mouse-x>;
y = <mouse-y>;

В любой момент времени x и y будут иметь координаты мыши. В отличие от нереактивного программирования, нам нужно сделать это назначение только один раз, и переменные x и y будут автоматически обновляться. Вот почему реактивное программирование и функциональное программирование так хорошо работают вместе: реактивное программирование устраняет необходимость изменять переменные, при этом позволяя вам делать многое из того, что вы могли бы сделать с помощью мутаций переменных.

Если мы затем сделаем некоторые вычисления на основе этого, результирующие значения также будут значениями, которые меняются со временем. Например:

minX = x - 16;
minY = y - 16;
maxX = x + 16;
maxY = y + 16;

В этом примере minX всегда будет на 16 меньше, чем координата x указателя мыши. С библиотеками, поддерживающими реактивность, вы могли бы сказать что-то вроде:

rectangle(minX, minY, maxX, maxY)

И прямоугольник размером 32x32 будет нарисован вокруг указателя мыши и будет отслеживать его, где бы он ни двигался.

Вот довольно хорошая статья по функциональному реактивному программированию .

739
ответ дан 19 December 2019 в 20:16
поделиться

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

Это очень хорошо подходит, например, для дизайна пользовательского интерфейса. , в котором прикосновение к элементу управления пользовательского интерфейса (например, к регулятору громкости в приложении для воспроизведения музыки) может потребовать обновления различных элементов отображения и фактической громкости вывода звука. Когда вы изменяете объем (скажем, ползунок), это соответствует изменению значения, связанного с узлом в ориентированном графе.

Различные узлы, имеющие ребра от этого узла «значения объема», будут автоматически запускаться, и любые необходимые вычисления и обновления, естественно, будут проходить через приложение. Приложение «реагирует» на раздражитель пользователя. Функциональное реактивное программирование было бы просто реализацией этой идеи на функциональном языке или вообще в рамках парадигмы функционального программирования.

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

Когда узел запускается или выполняет свои вычисления, соответствующие входы узлов, подключенных к его выходам, «активированы» или «отмечены». Любой узел, все входы которого активированы / отмечены / доступны, автоматически срабатывает. График может быть неявным или явным, в зависимости от того, как именно реализовано реактивное программирование.

Узлы можно рассматривать как запускаемые параллельно, но часто они выполняются последовательно или с ограниченным параллелизмом (например, может быть несколько потоков, выполняющих их). Известным примером была Manchester Dataflow Machine , которая (IIRC) использовала архитектуру тегированных данных для планирования выполнения узлов в графе через один или несколько исполнительных блоков. Вычисления потока данных довольно хорошо подходят для ситуаций, в которых запуск вычислений асинхронно, вызывая каскады вычислений, работает лучше, чем попытка управлять выполнением с помощью часов (или часов).

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

«нереактивна». программирование было бы программированием с совершенно другим взглядом на поток выполнения и отношение к внешним входам. Скорее всего, это будет несколько субъективно, поскольку люди, скорее всего, захотят сказать все, что реагирует на внешние сигналы, «реагирует» на них. Но если посмотреть на суть дела, программа, которая опрашивает очередь событий с фиксированным интервалом и отправляет любые обнаруженные события функциям (или потокам), менее реактивна (потому что она касается только пользовательского ввода с фиксированным интервалом). Опять же, в этом суть дела: можно представить, как внедрить реализацию опроса с быстрым интервалом опроса в систему на очень низком уровне и запрограммировать реактивную программу поверх нее.

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

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

71
ответ дан 19 December 2019 в 20:16
поделиться

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

Это обязательно очень многое упускает. Метафора быстро разрушается, когда вы фактически используете систему FRP. Во-первых, обычно также предпринимаются попытки смоделировать дискретные события (например, щелчок мышью). Я помещаю это здесь только для того, чтобы вы поняли, на что это похоже.

144
ответ дан 19 December 2019 в 20:16
поделиться

Книга Пола Худака The Haskell School of Expression - не только прекрасное введение в Haskell, но и довольно много времени посвящена FRP. Если вы новичок в FRP, я настоятельно рекомендую его, чтобы дать вам представление о том, как работает FRP.

Есть также то, что выглядит как новая версия этой книги (выпущена в 2011 г., обновлена ​​в 2014 г.), Музыкальная школа Хаскелла .

14
ответ дан 19 December 2019 в 20:16
поделиться
Другие вопросы по тегам:

Похожие вопросы: