reactive-banana icon indicating copy to clipboard operation
reactive-banana copied to clipboard

fmap (fmap f) (stepper a b) performs differently to stepper (f a) (fmap f b)

Open ocharles opened this issue 9 years ago • 3 comments
trafficstars

We have the following code in our application:

      currentFrame <-
        let undistort' img = undistort img cameraMatrix distCoeffs
        in fmap (fmap undistort')
                (stepper (appImage initialAppData) newFrameAcquired)

undistort is a relatively expensive operation, taking around 0.3s. Therefore it's important that we only distort when we really need to - which is exactly when a new frame comes in (from a web camera). However, the above seems to result in undistort being evaluated whenever I need the value of the currentFrame Behavior. The following rewrite evaluates undistort only when necessary:

      currentFrame <-
        let undistort' img = undistort img cameraMatrix distCoeffs
        in stepper (undistort' (appImage initialAppData)) (fmap undistort' newFrameAcquired)

Is this to be expected?

ocharles avatar Aug 31 '16 13:08 ocharles

It does look undesirable to me that the two code snippets have different performance characteristics.

At the moment, the internal implementation of reactive-banana uses different mechanisms for memoizing Event compared to memoizing Behavior values. The latter is not quite as good as the former, hence the discrepancy. That said, I'm not entirely sure why it doesn't work in this case.

HeinrichApfelmus avatar Aug 31 '16 16:08 HeinrichApfelmus

I was unable to reproduce this with the following code:

import Control.Monad
import Reactive.Banana
import Reactive.Banana.Frameworks
import System.IO.Unsafe
import Control.Concurrent

main :: IO ()
main = do
  (ah, fire) <- newAddHandler
  (ah2, fire2) <- newAddHandler

  (actuate =<<) . compile $ do
    eEnter <- fromAddHandler ah
    eFrame <- fromAddHandler ah2

    bNum <- (fmap.fmap) expensive (stepper 0 eFrame)

    reactimate (putStrLn "frame" <$ eFrame)
    reactimate (print <$> bNum <@ eEnter)

  void . forkIO . forM_ [1..] $ \i -> do
    threadDelay (3*1000*1000)
    fire2 i

  forever $ getLine >> fire ()

expensive :: Int -> Int
expensive n =
  unsafePerformIO $ do
    putStrLn "expensive calculation!"
    threadDelay (1*1000*1000)
    pure (n*2)
{-# NOINLINE expensive #-}

Running this, "frames" arrive every 3 seconds, and pressing enter prints the value of the behavior that is updated by an expensive pure function every frame.

If I run this and press enter a bunch, I only see the expensive operation computed once per new frame, and between frames, it's cached.

mitchellwrosen avatar Jun 28 '18 05:06 mitchellwrosen

May be a duplicate of https://github.com/HeinrichApfelmus/reactive-banana/issues/251

ocharles avatar Sep 04 '22 10:09 ocharles