Збереження держави без призначення


10

Я вивчаю функціональне програмування, і у мене виникають проблеми з розумінням того, як реалізуються окремі сценарії без використання завдання. Наступна проста проблема в значній мірі підсумовує мою плутанину.

Напишіть програму, яка отримує події про зміни в заданій структурі даних і випромінює події, коли ця структура даних досягає певного стану.

Тож у мене є копія структури даних, яку я підтримую

datastructure_copy::DataStructure 

У мене є потік подій, які запускаються, коли він змінюється:

datastructure_changes::Stream Change

У мене є функція, яка застосовує зміни до структури даних і повертає нову її копію:

apply_change::Change -> DataStructure -> DataStructure

І в мене є присудок, який перевіряє, чи стан даних досяг бажаного стану.

is_ready::DataStructure ->Boolean

Іншими словами, мені потрібно щось на кшталт "зменшити", що працює на потоках.

Я знаю, що один із способів здійснити це - перерахувати стан кожного разу, коли настає зміна, однак це видається недоцільним. Я трохи грав з монадою штату, але мені здається, що це призначено для вирішення іншої проблеми.

То чи є інший спосіб зробити це?

Зауважте, що моє запитання є чисто концептуальним, і я не глибоко знайомий з Haskell.


Так чи інакше, ви ніколи не побачите "призначення" (відзначте різницю між присвоєнням і прив'язкою) в Haskell, оскільки "Haskell не має поняття" присвоєння "," змінного стану "або" змінних "і є" чистим " функціональна мова "" . Держава Монада повинна бути тим, що ви шукаєте, вам просто потрібно навчитися її використовувати. Якщо ви хочете, я можу дати більш вичерпну відповідь сьогодні сьогодні.
Франческо Грамано

Відповіді:


2

Я знаю, що один із способів здійснити це - перерахувати стан кожного разу, коли настає зміна, однак це видається недоцільним.

Якщо зміни, застосовані під час події, не є розподільними, в тій чи іншій мірі вам доведеться перераховувати стан кожного разу, коли трапляється подія, оскільки кінцевий стан є не що інше, як початковий стан плюс послідовні зміни. І навіть якщо зміни є розповсюджуючими, ти, як правило, хочеш послідовно перетворити стан у наступний, оскільки ти хочеш зупинити процес так само швидко, як досягнуто заданий стан, і оскільки ти повинен обчислити наступний стан, щоб визначити, чи новий - розшукова держава.

У функціональному програмуванні зміни стану зазвичай представлені викликами функцій та / або параметрами функції.

Оскільки ви не можете передбачити, коли буде обчислено кінцевий стан, не слід використовувати не хвостову рекурсивну функцію. Потік станів, в якому кожна держава базується на попередньому, може бути хорошою альтернативою.

Тож у вашому випадку я відповів би на наступний код у Scala:

import scala.util.Random

val initState = 0.0
def nextState(state: Double, event: Boolean): Double = if(event) state + 0.3 else state - 0.1 // give a new state
def predicate(state: Double) = state >= 1

// random booleans as events
// nb: must be a function in order to force Random.nextBoolean to be called for each  element of the stream
def events(): Stream[Boolean] = Random.nextBoolean #:: events()  

val states: Stream[Double] = initState #:: states.zip(events).map({ case (s,e) => nextState(s,e)}) // a stream of all the successive states

// stop when the state is >= 1 ;
// display all the states computed before it stopped
states takeWhile(! predicate(_)) foreach println 

Що може дати, наприклад (я спростив вихід):

0.0
0.3
0.2
0.5
0.8

val states: Stream[Double] = ... - це лінія, де обчислюються послідовні стани.

Перший елемент цього потоку - початковий стан системи. zipоб'єднує потік станів із потоком подій у єдиний потік пар елементів, кожна пара - це (стан, подія). mapперетворює кожну пару в єдине значення, будучи новим станом, обчислюється як функція старого стану та пов'язаної з ним події. Отже, новий стан - це попередньо обчислений стан, а також пов'язана з ним подія, яка "модифікує" стан.

Таким чином, ви визначаєте потенційно нескінченний потік станів, кожен новий стан є функцією останнього обчисленого стану та нової події. Оскільки потоки ледачі в Scala (серед інших), обчислюються лише за потребою, тому вам не потрібно обчислювати непотрібні стани, і ви можете обчислити скільки завгодно станів.

Якщо вас цікавить лише перший стан, який поважає предикат, замініть останній рядок коду на:

states find predicate get

Що отримує:

res7: Double = 1.1

Чи можете ви дати трохи уявлення про лінію, яка робить магію:val states: Stream[Double]...
Боббі Маринофф,

Звичайно. Будь ласка, подивіться на мою редакцію.
mgoeminne

1

Ви кажете, що у вас є 2 функції:

apply_change::Change -> DataStructure -> DataStructure
is_ready::DataStructure ->Boolean

і якщо я зрозумів, що ви правильно, is_readyце досить дорого, тому ви не хочете робити цього для кожної події зміни знову і знову.

Вам потрібна функція, яка приймає початкову структуру DataStructure і конденсує її в простий стан і функцію, яка приймає конденсований стан, a Change і виводить новий конденсований стан.

Скажімо, DataStructure - це триплет, x,y,zі ви чекаєте, що x, y і z будуть простими числами. Тоді ваш конденсований стан може бути набором, з яких x, y, z не є простими. Зміна, яка робить x prime, видаляє x із набору. Зміна, яка робить x не простим, додає x до набору (якщо його немає). DataStructure готова, тоді набір порожній.

Ідея полягала б у тому, що оновлення конденсованого стану набагато дешевше, ніж оновлення DataStructure та обчислення є вже з нуля.

Примітка: Ще кращим підходом може бути відстеження того, який з x, y, z де перевіряється на прем'єр-мінімум, а якщо - де. Для кожної зміни ви позначите відповідне поле як не встановлене. Тоді, коли називається is_ready, ви перевіряєте та запам'ятовуєте. Це краще, якщо ви не перевіряєте is_ready після кожної зміни, оскільки x може змінюватися кілька разів, і ви перевіряєте праймер лише один раз.

Використовуючи наш веб-сайт, ви визнаєте, що прочитали та зрозуміли наші Політику щодо файлів cookie та Політику конфіденційності.
Licensed under cc by-sa 3.0 with attribution required.