kasbah
kasbah

Reputation: 943

Creating Applicative instance of a stateful signal type with Elerea

I am hacking around in Helm trying to create a stateful signal type that will carry additional info on whether the value has changed since the last sampling. I have managed to make the new signal type an instance of Functor but I am struggling to define the <*> function of Applicative which would allow me to lift multiple signals into one function.

The only signal I have defined so far is the constant signal that would have a Changed x on the initial sampling and an Unchanged x thereafter. Additional signals would be more complex and use Elerea.stateful to determine whether the value has changed.

lift2 wouldn't work in the code below because <*> hasn't been defined yet.

import qualified FRP.Elerea.Simple as Elerea
import Control.Applicative

data Event a = Changed a | Unchanged a

instance Functor Event where
    fmap f (Changed a)   = Changed (f a)
    fmap f (Unchanged a) = Unchanged (f a)

instance Applicative Event where
    pure = Unchanged
    (Changed   f) <*> (Changed   x) = Changed (f x)
    (Changed   f) <*> (Unchanged x) = Changed (f x)
    (Unchanged f) <*> (Changed   x) = Changed (f x)
    (Unchanged f) <*> (Unchanged x) = Unchanged (f x)

data Signal a = Signal (Elerea.SignalGen (Elerea.Signal (Event a)))

instance Functor Signal where
    fmap f (Signal x) = Signal ((fmap . fmap . fmap) f x)

instance Applicative Signal where
    pure = Signal . pure . pure . pure
    --(<*>) = ??

{-| Creates a signal that never changes. -}
constant :: a -> Signal a
constant x = Signal $ Elerea.stateful (Changed x) (\_ -> Unchanged x)

lift :: (a -> b) -> Signal a -> Signal b
lift = fmap

lift2 :: (a -> b -> c) -> Signal a -> Signal b -> Signal c
lift2 f a b = fmap f a <*> b

Full code that I am working with currently is on Github.

Upvotes: 2

Views: 136

Answers (1)

Reite
Reite

Reputation: 1667

The signature of pure . pure . pure is:

(Applicative f, Applicative f1, Applicative f2) => a -> f (f1 (f2 a))

So you just want to lift the <*> through two additional applicative layers. You need a function with the signature:

(Applicative f, Applicative f1, Applicative f2) => f (f1 (f2 (a -> b))) -> f (f1 (f2 a)) -> f (f1 (f2 b))

This function can be made by applying liftA2 twice, so please try this:

(Signal f) <*> (Signal x) = Signal $ (liftA2 (liftA2 (<*>))) f x

Upvotes: 1

Related Questions