javascripthaskellghcjsghcjs-dom

removing the current listener from within EventM


Suppose I want to create, using ghcjs-dom, an event listener which responds to a click, and then removes itself.

I have

addListener :: (IsEventTarget t, IsEvent e)
            => t -> EventName t e -> SaferEventListener t e -> Bool -> IO ()
removeListener :: (IsEventTarget t, IsEvent e)
            => t -> EventName t e -> SaferEventListener t e -> Bool -> IO ()

to add and remove, and

newListener :: (IsEvent e) => EventM t e () -> IO (SaferEventListener t e)

to construct the listener out of an EventM. How can I access the SaferEventListener (which I will only later construct) from within the EventM, in order to remove it when the event occurs?

In JavaScript, you use a named function expression as your callback to addEventListener, and then apply removeEventListener to that name from within the callback. But nothing analogus seems to be possible here. Or am I missing something?


Solution

  • Use fixIO

    fixIO $ \rec -> newListener _eventm
    

    Fill _eventm with your EventM, and you will be able to access the event listener that will eventually be created through the name rec. rec will be the result of the newListener call, but it can be "used" before that is executed. I say "used," because trying to force it with seq or anything stronger will cause an infinite loop, but you should be fine doing what you're doing.


    fixIO is a generalization of fix:

    -- the essence of recursion
    fix :: (a -> a) -> a
    fix f = let x = f x in x
    -- equivalent but less performant and less relevant
    fix f = f (fix f)
    
    -- if you have used JS's "named anonymous functions"
    -- this will seem very familiar
    (fix (\fact n ->
      if n <= 1 then 1 else n * fact (n - 1)
    )) 3 = 6
    -- JS:
    -- (function fact(n) {
    --   if(n <= 1) { return 1; } else { return n * fact(n - 1); }
    -- })(3) === 6
    
    -- but this has more power
    repeat = fix . (:)
    repeat 1 = fix (1:) = 
       let x = 1:x in x = 1:fix (1:) = [1,1,1,1,1,1,1,1,1,1,1,1,1,1...]
    
    fix id = let x = id x in x = let x = x in x = _|_ -- oops!
    
    fixIO :: (a -> IO a) -> IO a
    fixIO f = _ -- horrendous, unsafe code
    
    fixIO (\xs -> return $ 1:xs) = return [1,1,1,1,1,1,1,1,1,1...]
    
    fixIO return = fixIO (return . id) = return $ fix id = return _|_ -- oops!
    

    The idea of fix is making a function's eventual result available to it before it is actually created.
    The idea of fixIO is making an IO function's eventual result available to it before it is actually created while also carrying out some IO actions. Also, fixIO only carries out these actions once, which is why the first definition of fix (that only calls f once) is more relevant than the second.

    fixIO, in turn, is a specialization of mfix :: MonadFix m => (a -> m a) -> m a, where MonadFix is the class of monads (including IO, with mfix = fixIO) that admit such knot-tying semantics. GHC supports "recursive do" notation for any MonadFix:

    {-# LANGUAGE RecursiveDo #-}
    someCode = mdo ...
                   listener <- newListener _eventm -- can access listener in definition
                   ...
    -- or
    someCode = do ...
                  rec listener <- newListener _eventm
                  ...