2

Is there some way to write a function f :: (a -> b -> ... -> t) -> (Monad m => m a -> m b -> ... -> m t), basically liftMn for any n?

(EDIT: fixed nonsensical example.)

I'm writing an FRP library, and thought it'd be neat if I could have code vaguely like:

main = do
  input1 <- signalFromTextBoxTheUserMayTypeANumberInto
  input2 <- signalFromAnotherTextBox
  divided <- signal div input1 input2
  signal (\x -> showTextAlert ("input1 `div` input2 is " ++ show x)) divided

I've been fiddling with type families to get it working, but I'm starting to think that it's actually not doable. I'm currently doing something like this:

type Action a = IORef a -> IO ()
type Listener = IO ()
newtype Signal a = Signal (IORef (SigData a))

data SigData a = SigData {
    trigger   :: Action a,
    output    :: IORef a,
    listeners :: [Listener]
  }

class Sig a where
  type S a
  mkSig :: [AnySignal] -> a -> S a

instance Sig b => Sig (a -> b) where
  type S (a -> b) = Signal a -> S b
  mkSig dependencies f =
    \s@(Signal sig) ->
      let x  = unsafePerformIO $ readIORef sig >>= readIORef . output
      in mkSig (AnySignal s : dependencies) (f x)

instance Sig Int where
  type S Int = IO (Signal Int)
  out <- newIORef x
  self <- Signal <$> (newIORef $ SigData {
      trigger   = \ref -> writeIORef ref $! x,
      output    = out,
      listeners = []
    })
  mapM_ (self `listensTo`) deps
  return self

This obviously doesn't work, as the unsafePerformIO gets evaluated once and then keeps that value, and if did work it'd still be ugly, hacky and generally evil. Is there a way to do this, or will I just have to let go of the idea?

valderman
  • 8,365
  • 4
  • 22
  • 29
  • Ask your self: Can I give this combinator a reasonable type? If not, what kind of black magic is needed to make it have a reasonable type? This might be helpful: [How to create a polyvariadic haskell function?](http://stackoverflow.com/q/3467279/417501) – fuz Mar 22 '12 at 19:49

2 Answers2

10

I'm kind of new to all of this, so forgive me if this is a silly answer, but isn't this exactly what applicative functors are for?

Applicatives let you do something like:

f :: a -> b -> ... -> c

f2 :: Applicative p => p a -> p b ... -> p c
f2 x ... y = f <$> x <*> ... <*> y

if I'm not mistaken. (The ellipses are any number of types/arguments)

Geoff Huston
  • 363
  • 2
  • 6
  • 1
    Indeed. The example given would be `divided <- div <$> input1 <*> input2`. Applicative functors are ideal for FRP; the [reactive-banana](http://hackage.haskell.org/package/reactive-banana) and [sodium](http://hackage.haskell.org/package/sodium) libraries both take this approach. – ehird Mar 22 '12 at 18:48
  • Yes, that's the backup plan if I can't get this type trickery to work. It's not as pretty though. :( – valderman Mar 22 '12 at 18:54
  • 2
    @valderman, the type trickery will only get you in trouble. It has been done many times, and fails in all but the most concrete cases (i.e. it destroys your ability to abstract). This is what applicative functors are made for; I recommend them (and you will probably learn to find it prettier than what you suggest once you get a feel for the algebraic structure) – luqui Mar 23 '12 at 09:22
6

How about the Strathclyde Haskell Environment preprocessor, which lets you use idiom brackets, the original notation for applicative functors? This lets you use (| f a b c |) for f <$> a <*> b <*> c. Your example would be (| input1 `div` input2 |).

By the way, it's probably a bad idea for your Signal type to have a Monad instance; this causes the well-known (in the FRP community) problem of time leaks; see this blog post for more information. An Applicative interface is OK, but a Monad interface is not. There are several solutions that prevent time leaks while still allowing the same dynamic event switching behaviour, involving things like an additional type parameter or another monad (as seen in, e.g. the sodium library).

ehird
  • 40,602
  • 3
  • 180
  • 182
  • That would be an improvement over the _<*> everywhere_ notation, indeed. Also, thanks for the advice on the Monad instance! – valderman Mar 22 '12 at 19:10