Is it possible to manually update the value of a Behaviour? (Functional Reactive Programming, Threepenny)
Asked Answered
B

1

6

I really hope I haven't gone down a dead-end here. I have a Behaviour that gives the currently selected Color, and the current mouse coordinates, then carries out a task when the mouse is clicked. That task involves looking at a list and then updating the values in that list, for it to be retrieved later. The fact that I can "store" the selected color gives me hope that storing a list can be done in a similar manner. I'm just at a dead end and not sure how to solve this. Would really appreciate some help.

-- There is a Blue button and a Red button on our UI. Whichever
-- button was clicked last is our current color selection.
colorRedSelected = const ColorRed <$ UI.click redButton
colorBlueSelected = const ColorBlue <$ UI.click blueButton

-- we combine both the above Events to create a new one that tells us the current selected color
colorSelected = unionWith const colorRedSelected colorBlueSelected

-- accumulate values for our Behaviour, starting with ColorRed selected by default
colorMode       <- accumB ColorRed modeEvent

-- create a Behaviour
mouseCoordinate   <- stepper (0,0) $ UI.mousemove canvas

-- want to start with the list [1,2,3,4], but this value should change later.
-- I have 'never' here, as I don't know what else to put here yet.

listState      <- accumB ([1,2,3,4]) never

-- Combine the Behaviours, we now have a tuple (chosenColorMode, mouseCoordinateTuple, savedList)

let choices = (,,) <$> colorMode <*> mouseCoordinate <*> listState

-- Apply the event (of the user clicking the canvas) to the Behaviour,
-- creating a new Event that returns the above tuple when it fires    

makeChoice = choices <@ UI.click canvas

onEvent makeChoice $ \(colorMode, (x,y), savedList) -> do    
    ...
    -- in this block we use the savedList, and generate a newList.
    -- I want to update the choicePosition behaviour so that the newList
    -- replaces the old savedList.
Brawn answered 13/1, 2022 at 4:47 Comment(2)
I can't write a full answer right now, but here's the gist of it: the general principle is that we don't manually update values of behaviors, but rather specify how they will change when defining them. In your case, if listState is a Behavior [Integer], that means you want to replace never with an Event ([Integer] -> [Integer]) built from UI.click canvas (in a similar way to makeChoice), with the function carried by this event specifying how the list should change.Skullcap
I've made some great progress on this, thanks to your suggestion. Thank you! I'll post my new approach as soon as I get some free time.Brawn
B
2

Full credit to this response from duplode, I'll just go through how it was solved:

Let's say we have a function that modifies a list somehow, depending on some value. How/why updateMyList modifies the list doesn't really matter for this explanation, we just need to know its type. For this example, we'll say the value that determines how the list changes is a mouse coordinate tuple (x, y), which we'll pass as its first parameter:

updateMyList :: (Double, Double) -> [Integer] -> [Integer]
updateMyList (x, y) oldList = ...

If we have an Event that tells us the mouse coordinates when the user clicks:

mouseCoords :: Behavior (Double, Double)
mouseCoords <- stepper (0,0) $ UI.mousemove canvas

mouseClicked :: Event (Double, Double)
mouseClicked = mouseCoords <@ UI.click canvas -- this is the Event we need

What we need to do is fmap the list-updating function onto mouseClicked:

listChangeEvent = fmap updateMyList mouseClicked

So we've created a new Event: when mouseClicked is triggered, the mouse coordinates are passed as the first parameter to updateMyList, and that is the value of our new Event at that timestamp. But this is a partially applied function, updateMyList still requires an [Integer] as a parameter, so as a result, listChangeEvent has the following type:

listChangeEvent :: Event ([Integer] -> [Integer])

Now, this is the clever part: if we use accumB and specify the starting accumulator (i.e. our starting list, [1,2,3,4]), and then also use the above listChangeEvent as the Event accumB takes its value from:

listState      <- accumB ([1,2,3,4]) listChangeEvent

Then that accumulator is what will be passed to the function in Event ([Integer] -> [Integer]). Meaning the first time the listChangeEvent triggers, updateMyList will be called with:

updateMyList (x, y) [1,2,3,4] -- (x, y) being the mouse coordinates at that time

And the result of that becomes the new accumulator value in listState, and that new list will be used as the parameter to updateMyList the next time listChangeEventtriggers, and so on.

We can use this for anything at all, it doesn't necessarily have to be a list that we're modifying. This just gives us a way to initialize a Behavior with a value, and that we can specify exactly how the next value of the Behavior is derived, by creating a function that is equivalent to updateMyList.

Brawn answered 17/1, 2022 at 15:37 Comment(1)
Nicely written explanation; good answer :)Skullcap

© 2022 - 2024 — McMap. All rights reserved.