Предотвращение бесконечных циклов в Yampa/Animas с зависимостью SF друг от друга

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

type Position2 = Vector2 Float

boid :: Position2 -> SF Position2 Position2
boid s = loop (arr (uncurry seek) >>> integral >>> arr (^+^ s) >>> arr dup)

функция seek принимает два входа (из-за цикла). Текущая позиция и целевая позиция. Затем он просто создает вектор, указывающий от текущей позиции к целевой позиции с величиной 50, то есть со скоростью. Затем скорость интегрируется и добавляется начальное положение. В конце сигнал разделяется на две части, так что одна может стать выходом, а другая может вернуться в функцию поиска.

Теперь я могу определить boids следующим образом:

aBoid = constant (vector2 500 500) >>> (boid (vector2 600 60))
bBoid = aBoid >>> (boid (vector2 3 4))

Здесь aBoid ищет точку (500, 500), а bBoid ищет aBoid.

Моя проблема в том, что когда я хочу, чтобы два боида стремились друг к другу. Когда я делаю это:

aBoid = bBoid >>> (boid (vector2 600 60))
bBoid = aBoid >>> (boid (vector2 3 4))

Программа просто печатает: ProgramName: <<loop>>, что, как я предполагаю, означает, что она входит в бесконечный цикл.

Я также пытался использовать функцию par следующим образом:

sim :: SF a [Position2]
sim = loop (arr snd >>> par route [boid (vector2 10 10), boid (vector2 100 100)] >>> arr dup)

здесь функция route просто сопоставляет вывод каждого боида со входом другого (как zip, но со смещением 1)

Это также дает сообщение <<loop>>.

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

Я должен добавить, что я нахожу эту штуку FRP очень сложной и часто запутанной, поэтому я не совсем уверен, что вообще имею смысл ;)


person Andreas Vinter-Hviid    schedule 27.12.2011    source источник


Ответы (2)


Я не совсем знаком с Yampa/Animas, но похоже, что проблема в том, что у вас есть рекурсия без базового случая; вы описали эволюцию системы, но не то, как она начиналась.

Как насчет:

aBoid = bBoid >>> boid (vector2 600 60) >>> iPre (vector2 0 0)
bBoid = aBoid >>> boid (vector2 3 4)

так что aBoid начинается с (0,0), а затем цикл обратной связи начинается в следующее мгновение? Предполагается, что я понимаю iPre правильно...

(Конвейер может быть легче понять, если вы представите его с (.), перевернутой версией (>>>).)

person ehird    schedule 27.12.2011
comment
Я согласен, что я похож на то, что должно работать, к сожалению, это не так. Однако ваш ответ заставил меня взглянуть на функцию loopPre, которую я сейчас использую с подходом par, и это работает. Глядя на реализацию loopPre, я понял, что проблема в том, что iPre должен быть на стороне ввода, а не на стороне вывода, но я этого не проверял. - person Andreas Vinter-Hviid; 28.12.2011
comment
Ах; это сработает, если поставить iPre перед bBoid или сразу после него (перед boid)? Таким образом, я думаю, вместо этого будет установлено начальное значение для bBoid. Рад узнать, что у вас все получилось; надеюсь, я смогу отредактировать свой ответ во что-то, что работает :) - person ehird; 28.12.2011
comment
Я не могу заставить его работать. Однако я подозреваю, что это может быть связано с тем, что параллельная работа с сигнальными функциями требует большего, чем просто определение сигнальных функций в терминах друг друга. Кажется, вам действительно нужна некоторая сантехника для разделения сигнала, подачи его в каждую сигнальную функцию, а затем повторного сбора вывода в виде единого сигнала осмысленным образом. Поэтому я пришел к выводу, что использование функции типа par или pSwitch является правильным решением. Если бы вы использовали iPre, что я косвенно делаю через loopPre, кажется, это должно быть на стороне ввода. - person Andreas Vinter-Hviid; 28.12.2011
comment
Справедливо. Кажется, что это можно решить с помощью наблюдаемого совместного использования, чтобы переписать рекурсивные определения с помощью такой конструкции. Вы должны опубликовать и принять свое решение, чтобы помочь другим в будущем :) - person ehird; 28.12.2011

Итак, с небольшой помощью ehird я придумал кое-что, что работает.

Код выглядит следующим образом (здесь используется 3 боида вместо 2):

sim :: SF a [Position2]
sim = loopPre [zeroVector, zeroVector, zeroVector] (
    arr snd >>>
    par route [boid (vector2 10 10), boid (vector2 100 400), boid (vector2 500 500)] >>>
    arr dup)

Здесь функция маршрута работает так же, как та, которую я описал в вопросе.

Используя loopPre вместо loop, я могу дать каждому боиду начальную позицию, что решает проблему. Я думаю, что решение ehird не сработало по причине того, что при параллельном запуске нескольких сигнальных функций требуется некоторая сантехника, о которой заботится функция par.

Буду признателен за ответ от человека, который на 100% уверен в том, что происходит :)

Редактировать

Эта реализация sim немного красивее:

sim :: Int -> SF a [Position2]
sim num = loopPre (take num (repeat zeroVector)) (
    arr snd >>>
    par route (randomBoids num) >>>
    arr dup)

Где randomBoids num генерирует num случайных объектов.

person Andreas Vinter-Hviid    schedule 28.12.2011
comment
Кстати, take n . repeatreplicate n :) - person ehird; 31.12.2011