LINUX.ORG.RU

Худший случай для ФП?

 , , ,


0

1

Очень часто можно встретить такое мнение:

Для чего-нибудь алгоритмического или математического я использую ФП. Но для любого рода симуляции ООП решение гораздо проще.

Предположим, что симуляция означает много меняющихся вложенных акторов, например поле боя, где есть техника, содержащая солдат, у которых есть оружие. И даже техника состоит из колёс и прочих частей, которые могут быть независимо повреждены. Функциональный подход выглядит как головоломка. Что если нужно из кода танка изменить что-то во вне? Таскать ли с собой везде состояние мира?

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

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

Чтобы такого не происходило, надо отложить создание всех новых объектов до конца кадра.

Теперь, предположим, каждый танк принимает решение что делать основываясь на состоянии остальных объектов поблизости. Танк1 сканирует ближайшие объекты, двигается вперёд. Танк2 делает тоже самое и видит, что Танк1 слишком близко к нему, хотя на самом деле это не так, Танк2 имеет неправильные представления о положении Танк1, полученные как бы из будущего.

Для этой проблемы есть пара решений: разделить кадр на фазу принятия решений и на фазу перемещения. Второе — сохранить список всех изменений и потом атомарно применять его на финальном этапе.

Если бы симуляция сразу писалась в функциональном стиле, костылей, придуманных выше, вводить бы не пришлось. Это более естественный способ работы, когда у тебя нет мутабельных структур данных. Было бы ФП решение проще ООП? Может быть и нет. Хоть все обновления и отложены, остаётся вопрос как управляться с передаваемой вокруг информацией об изменениях.

Но если ФП-версия сначала казалась автору исходной копипасты невозможной, теперь это выглядит как естественный подход к подобным проблемам.

★★★★★

Последнее исправление: PolarFox (всего исправлений: 1)

Ответ на: комментарий от PolarFox

Объекты не существуют в вакууме. У тебя всё равно должен быть список объектов. Так вот что помешает на каком-то шаге вернуть в этом списке больше объектов, чем было?..

anonymous
()
Ответ на: комментарий от PolarFox

А табличные процессоры - очень узкая аналогия, не отражающая, к примеру, события.

anonymous
()

Если бы симуляция сразу писалась в функциональном стиле, костылей, придуманных выше, вводить бы не пришлось.

просто потому, что эти костыли введены в самом ЯП. Танк1 принимает решение в контексте прошлого состояния Танк2, которое Танк1 сам и сохраняет в своём контексте. Потому _кажется_ что всё работает без костылей. IRL это ФП жрёт на порядок больше памяти и работает на порядок медленнее. За то код красивый и простой.

Но если ФП-версия сначала казалась автору исходной копипасты невозможной, теперь это выглядит как естественный подход к подобным проблемам.

потому что на 100 танчиков мы имеем сейчас не 640К, а все 4 000 000К как минимум.

emulek
()
Ответ на: комментарий от emulek

Ну дык. Нафига экономить на спичках? Кармак на квейкконе, когда рассуждал про хаскель, схему и ФП в целом, сказал что состояние игры — от силы 4-8 мбайт. Чтобы хранить два состояния, памяти хватает. GC в традиционном стиле опять же не нужен, из старого состояния в новое перейдут только используемые объекты.

PolarFox ★★★★★
() автор топика

Худший случай для ФП - это что угодно, что не факториал или числа Фибоначчи.

anonymous
()
Ответ на: комментарий от emulek

потому что на 100 танчиков мы имеем сейчас не 640К, а все 4 000 000К как минимум.

У тебя с этим проблемы? Современное железо чуть быстрее того, что было раньше. Количество багов на 1000 строк кода — константа независимо от языка, следовательно, короче программа — меньше багов. Дебажить, опять же, проще.

x3al ★★★★★
()

Если бы симуляция сразу писалась в функциональном стиле, костылей, придуманных выше, вводить бы не пришлось.

Можно подробнее? Используем подход с фреймами, нужна возможность посмотреть состояние игрового поля в каждом фрейме. Чем нам помогут иммутабельные структуры данных в решении описанной проблемы?

Да и как это вообще будет выглядеть? Перемещение танчика будет возвращать новый контекст?

Где можно посмотреть пример подобных симуляций, написанных в ФП?

anonymous
()
Ответ на: комментарий от anonymous

the movement routine would return a list of any of these side effects:

{new_position, Coordinates} 
{ate_ghost, GhostName}
{ate_dot, Coordinates} 
ate_fruit
killed_by_ghost
All of a sudden things are a lot simpler. You can pass in the relevant parts of the state of the world, and get back a simple list detailing what happened. Actually handling what happened is a separate step, one that can be done later on in the frame. The advantage here is that changes to core world data don't have to be painstakingly threaded in and out of all functions in the game.

Ⓒ оно же, http://prog21.dadgum.com/26.html (начало — http://prog21.dadgum.com/23.html)

x3al ★★★★★
()
Последнее исправление: x3al (всего исправлений: 1)
Ответ на: комментарий от x3al

константа независимо от языка, следовательно, короче программа — меньше багов. Дебажить, опять же, проще.

Ну подебажь код на APL или Brainfuck, умнег.

anonymous
()
Ответ на: комментарий от x3al

the movement routine would return a list of any of these side effects

Это то же самое, что и:

сохранить список всех изменений и потом атомарно применять его на финальном этапе

anonymous
()
Ответ на: комментарий от anonymous

inb4 предвзятость к жабоскрипту

Что думают хипстеры по поводу FRP и фреймов

Instead of using JavaScript objects, Om uses ClojureScript data structures which we know will not be changed. Because of this, we can provide a component that implements shouldComponentUpdate by doing the fastest check possible - a reference equality check. This means we can always determine if the paths changed, starting from the root, in logarithmic time.

x3al ★★★★★
()

Кармак говорил о чем-то подобном на QuakeCon.

The objects are passed in a reference to a static copy of the world, and themselves. And they return their new version at the end. They cant break anybody else, cus they cant touch anything else. Its not allowed by the compiler.

The obvious question is how do you shoot somebody if you cant affect them. What you do is you say, well, im firing my gun, I hit him, the world says I do, I wanna make him die. So you have to make an event of some kind, that gets communicated to the other entity.

In Haskell, its just a partially evaluated function that takes an entity as a parameter and returns another entity. Eg, you can have a ‘Do Damage’ function, you partially evaluate it with your 5 points of damage that is going to be dealt, and then you pass it and its just going to take the last function parameter as the entity and you set that up on its own personal list. So every entity sets up a list of all the things they are going to do to anybody, and then at the beginning of the next frame you gather everything together, distribute it all to the entities, and then; The first thing they do, you’ve got the world, you’ve got the list of events that affect them, apply them all, one after another, generating a new copy of themselves, and then They do their thinking and processing, creating the final version that goes back into the world.

unsigned ★★★★
()
Ответ на: комментарий от anonymous

Да и как это вообще будет выглядеть? Перемещение танчика будет возвращать новый контекст?

Будет выглядеть примерно также как в ООП с предложенными костылями: сначала все танчики думают, потом все танчики двигаются, потом разруливаем столкновения и подобные проблемы.

Псевдокод на псевдолиспе:

(defn simulation [state step]
    (conj (newobjects state step) ; add new objects
          (remove nil? (map #(update state % step) state))))
          ; update all objects, nil objects removed from simulation

update — функция которая для определённого состояния системы, определённого объекта в ней и определённого шага отдаёт новое состояние объекта (или nil, если объект больше не должен существовать в симуляции).

newobjects возвращает все снаряды и прочие новые для симуляции объекты.

simulation возвращает новое состояние системы для старого состояния и шага.

PolarFox ★★★★★
() автор топика
Ответ на: комментарий от PolarFox

update — функция которая для определённого состояния системы, определённого объекта в ней и определённого шага отдаёт новое состояние объекта (или nil, если объект больше не должен существовать в симуляции).

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

anonymous
()
Ответ на: комментарий от anonymous

В одной симуляции у меня первый танчик поехал налево, в другой направо.

Херовая у тебя значит симуляция. Если у тебя симуляция зависит от случайных величин, то следует таскать с собой seed.

PolarFox ★★★★★
() автор топика
Ответ на: комментарий от PolarFox

Херовая у тебя значит симуляция.

Значит ты только факториалы симулируешь и поедание фиксированной тарелки борща сферическим школьником на псевдолиспе.

anonymous
()
Ответ на: комментарий от anonymous

В одной симуляции у меня первый танчик поехал налево, в другой направо.

У тебя танчик едет налево и направо одновременно? Или что значит «в одной» и «в другой», яннп.

anonymous
()
Ответ на: комментарий от PolarFox

Херовая у тебя значит симуляция.

Ага, пойди, поучи разработчиков GEANT ибаццо.

anonymous
()
Ответ на: комментарий от PolarFox

Ну дык. Нафига экономить на спичках? Кармак на квейкконе, когда рассуждал про хаскель, схему и ФП в целом, сказал что состояние игры — от силы 4-8 мбайт. Чтобы хранить два состояния, памяти хватает. GC в традиционном стиле опять же не нужен, из старого состояния в новое перейдут только используемые объекты.

дык я и говорю, что этот твой ФП годен только для игрушек в 100 танчиков. А в реальной задаче оно сливает со страшной силой, быстро, стремительно.

emulek
()
Ответ на: комментарий от anonymous

У тебя танчик едет налево и направо одновременно? Или что значит «в одной» и «в другой», яннп.

Нет. На каком-то шаге. В одной симуляции на первом шаге танчик повернул налево, в другой симуляции направо. При одинаковых исходных данных.

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

Метод предложенный полярным школьником будет на каждом шаге пересчитывать абсолютно все события. Либо их надо будет хранить в глобальном контексте, что дает нам те же самые костыли, предложенные автором для ООП-решения.

anonymous
()
Ответ на: комментарий от x3al

У тебя с этим проблемы?

да.

Современное железо чуть быстрее того, что было раньше.

автор бессовестно передёргивает. Атари бейсик был на два порядка примитивнее питона с точки зрения парсинга, потому такие алгоритмы и работают настолько медленно. К тому же, это не играет роли, ибо ВСЕ алгоритмы реализованы в батарейках на сишке, а если батареек нет, их делают самостоятельно(как например в mercurial). Ну а программу на сишечке сравнивать просто смешно даже с древним вылизанным асмовским кодом.

Количество багов на 1000 строк кода — константа независимо от языка

дело не в числе строк. Дело в том, что память тоже можно использовать для оптимизации. Всякие твои GC в жабе сегодня работают лишь потому, что памяти у нас МНОГО, и потому можно к ней наплевательски относится.

ЗЫЖ я не говорю, что ФП это плохо, это хорошо. Но это «хорошесть» не совсем заслуга самого ФП. Компьютеры стали мощнее, и программисту уже не нужно мучиться с кодом. Даже пузырьковая сортировка работает на зеноне.

emulek
()
Ответ на: комментарий от anonymous

Да и как это вообще будет выглядеть? Перемещение танчика будет возвращать новый контекст?

Танк1 перемещается в контексте, в котором Танк2 неподвижен. А Танк2 перемещается при неподвижном Танк1. В SICP ЕМНИП есть простой пример, с банковскими аккаунтами.

emulek
()
Ответ на: комментарий от anonymous

В одной симуляции на первом шаге танчик повернул налево, в другой симуляции направо. При одинаковых исходных данных.

значит случайность должна быть одинаковой.

emulek
()
Ответ на: комментарий от emulek

дык я и говорю, что этот твой ФП годен только для игрушек в 100 танчиков.

расскажи это например ejabberd-у

anonymous
()
Ответ на: комментарий от emulek

значит случайность должна быть одинаковой.

Если тебя смущает случайность, пусть это будет пользовательский ввод. Например, пользователь на 10м фрейме развернул танк.

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

anonymous
()
Ответ на: комментарий от anonymous

Естественно, надо сохранять запись всей симуляции с возможностью пофреймовой прокрутки в обе стороны и изменения каких-либо параметров в любом шаге.

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

PolarFox ★★★★★
() автор топика
Ответ на: комментарий от anonymous

Нет. На каком-то шаге. В одной симуляции на первом шаге танчик повернул налево, в другой симуляции направо.

Ну то есть, не другая симуляция, а другой шаг. И в чем затруднение?

При одинаковых исходных данных.

Нет, это уже разные исходные данные.

Либо их надо будет хранить в глобальном контексте

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

будет на каждом шаге пересчитывать абсолютно все события.

Все события, произошедшие на данном шаге, да. Это норамльно. Это как раз позволяет ограничиться обсчетом только тех состояний, которые реально изменились.

что дает нам те же самые костыли, предложенные автором для ООП-решения.

По факту — да, от состояний ты никуда не денешься, только в такой форме это уже не костыли. Ну или — менее костыльные костыли.

anonymous
()
Ответ на: комментарий от emulek

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

А Танк2 перемещается при неподвижном Танк1.

vada ★★★★★
()
Ответ на: комментарий от anonymous

По факту — да, от состояний ты никуда не денешься, только в такой форме это уже не костыли.

Как обычно. В теории мы делаем fold zip'нутых состояний танчиков и бесконечного списка тарелок с борщом, а на деле таскаем всюду глобальный синглтон.

anonymous
()
Ответ на: комментарий от anonymous

расскажи это например ejabberd-у

И где ты там ФП нашел? Состояние на состоянии сидит и состоянием погоняет.

anonymous
()
Ответ на: комментарий от anonymous

Как обычно. В теории мы делаем fold zip'нутых состояний танчиков и бесконечного списка тарелок с борщом, а на деле таскаем всюду глобальный синглтон.

В теории анонимус весь такой мегаархитектор с бесконечным списком базвордов, а на деле — золотце с прогрессирующей дислексией.

anonymous
()
Ответ на: комментарий от anonymous

В теории анонимус весь такой мегаархитектор с бесконечным списком базвордов, а на деле — золотце с прогрессирующей дислексией.

разработчики GEANT ИТТ

anonymous
()
Ответ на: комментарий от anonymous

еще больше базвордов в этот итт тред!!!

anonymous
()

Факт остается фактом - изобразить иммутабельность на ООП будет всё равно проще, чем мутабельность на ЧФП.

yoghurt ★★★★★
()

Для ФП очень неудобным является случай, когда надо в зависимости от некоторых условий изменять много состояний.

Например надо разобрать дату по http://tools.ietf.org/search/rfc6265#section-5.1.1

Получается что-то вроде:

(let loop ([time #f] [day #f] [month #f] [year #f]
           [tokens tokens])
  (if (null? tokens) 
      (values time day month year)
      (let ([token (car tokens)])
        (cond
          [(and (not time) (parse-time token)) => (λ (time) (loop time day month year (cdr tokens)))]
          [(and (not day) (parse-day token)) => (λ (day) (loop time day month year (cdr tokens)))]
          [(and (not month) (parse-month token)) => (λ (month) (loop time day month year (cdr tokens)))]
          [(and (not year) (parse-year token)) => (λ (year) (loop time day month year (cdr tokens)))]
          [else (loop time day month year (cdr tokens))]))))

В то время как не-ФП

(let ([time #f] [day #f] [month #f] [year #f])
  (map 
    (λ (token) 
      (cond
        [(and (not time) (parse-time token)) => (set! time <>)]
        [(and (not day) (parse-day token)) => (set! day <>)]
        [(and (not month) (parse-month token)) => (set! month <>)] 
        [(and (not year) (parse-year token)) => (set! year <>)]))
    tokens)
  (values time day month year))
гораздо лаконичней и очевидней

monk ★★★★★
()
Последнее исправление: monk (всего исправлений: 1)
Ответ на: комментарий от yoghurt

Еще проще — не городить костылей без надобности.

А вообще сильно зависит от применяемых для изображения средств.

anonymous
()
Ответ на: комментарий от vada

не плохо бы немного почитать учебник

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

emulek
()
Ответ на: комментарий от anonymous

Оно там глобальное.

ахринеть... А не говнокода нету поучится?

emulek
()
Ответ на: комментарий от tailgunner

Когда он сосал мамкину сиську, тебя еще в проекте не было.

защитничек нарисовался...

emulek
()
Ответ на: комментарий от x3al

Количество багов на 1000 строк кода — константа независимо от языка, следовательно, короче программа — меньше багов. Дебажить, опять же, проще.

Это кто тебе сказал? Количество багов зависит не от длины программы а от плотности смыслового наполнения. В том же хаскеле багов на 1клок на порядок больше чем в той же жабе будет, особенно если используются все прелести хаскиарта.

anonymous
()

Чтобы такого не происходило, надо отложить создание всех новых объектов до конца кадра.

Нет, надо просто добавить это в правила игры - то есть накапливать события к следующему ходу, позволять другим акторам видеть накопленные события и принимать решения в зависимости от них, а потом разрешать в порядке стека. Это отработанная система.

anonymous
()
Ответ на: комментарий от monk

С ленсами будет примерно одинаково

anonymous
()

Для этой проблемы есть пара решений: разделить кадр на фазу принятия решений и на фазу перемещения. Второе — сохранить список всех изменений и потом атомарно применять его на финальном этапе.

Это типовые решения, ничего костыльного в них нет.

Manhunt ★★★★★
()
Вы не можете добавлять комментарии в эту тему. Тема перемещена в архив.