content: -- |
  -- Description : Test cases for hybrid signal functions
  -- Copyright   : (c) Antony Courtney and Henrik Nilsson, Yale University, 2003-2004
  -- Authors     : Antony Courtney and Henrik Nilsson
  module Test.FRP.Yampa.Hybrid
      ( tests
      )
    where
  
  import Data.Maybe (fromJust)
  import Data.Tuple (swap)
  
  import Test.QuickCheck
  import Test.QuickCheck.Function
  import Test.Tasty (TestTree, testGroup)
  import Test.Tasty.QuickCheck (testProperty)
  
  import FRP.Yampa as Yampa
  
  import FRP.Yampa.LTLFuture (evalT, TPred (Next, Always, SP))
  import FRP.Yampa.QuickCheck (uniDistStream, uniDistStreamMaxDT)
  import FRP.Yampa.Stream
  
  import TestsCommon
  
  tests :: TestTree
  tests = testGroup "Regression tests for FRP.Yampa.Hybrid"
    [ testProperty "hold (0, fixed)"          (property $ wfg_t0 ~= wfg_t0r)
    , testProperty "hold (1, fixed)"          (property $ wfg_t1 ~= wfg_t1r)
    , testProperty "dHold (0, fixed)"         (property $ utils_t0 ~= utils_t0r)
    , testProperty "dHold (1, fixed)"         (property $ utils_t1 ~= utils_t1r)
    , testProperty "trackAndHold (0, fixed)"  (property $ wfg_t2 ~= wfg_t2r)
    , testProperty "trackAndHold (1, fixed)"  (property $ wfg_t3 ~= wfg_t3r)
    , testProperty "dTrackAndHold (0, fixed)" (property $ utils_t2 ~= utils_t2r)
    , testProperty "dTrackAndHold (1, fixed)" (property $ utils_t3 ~= utils_t3r)
    , testProperty "accum (0, fixed)"         (property $ accum_t0  == accum_t0r)
    , testProperty "accum (1, fixed)"         (property $ accum_t1  == accum_t1r)
    , testProperty "accum (2, fixed)"         (property $ accum_t2  == accum_t2r)
    , testProperty "accum (3, fixed)"         (property $ accum_t3  == accum_t3r)
    , testProperty "accum (8, fixed)"         (property $ accum_t8  == accum_t8r)
    , testProperty "accum (9, fixed)"         (property $ accum_t9  == accum_t9r)
    , testProperty "accum (11, fixed)"        (property $ accum_t11 == accum_t11r)
    , testProperty "accum (10, fixed)"        (property $ accum_t10 == accum_t10r)
    , testProperty "accum (12, fixed)"        (property $ accum_t12 == accum_t12r)
    , testProperty "accum (4, fixed)"         (property $ accum_t4  == accum_t4r)
    , testProperty "accum (5, fixed)"         (property $ accum_t5  == accum_t5r)
    , testProperty "accum (6, fixed)"         (property $ accum_t6  == accum_t6r)
    , testProperty "accum (7, fixed)"         (property $ accum_t7  == accum_t7r)
    , testProperty "accum (13, fixed)"        (property $ accum_t13 == accum_t13r)
    , testProperty "accum (14, fixed)"        (property $ accum_t14 == accum_t14r)
    , testProperty "accum (15, fixed)"        (property $ accum_t15 == accum_t15r)
    , testProperty "accum (16, fixed)"        (property $ accum_t16 == accum_t16r)
    , testProperty "accum (17, fixed)"        (property $ accum_t17 == accum_t17r)
    , testProperty "accumHold (0, qc)"        testAccumHold1
    , testProperty "accumHold (1, qc)"        testAccumHold2
    , testProperty "dAccumHold (0, qc)"       testDAccumHold1
    , testProperty "dAccumHold (1, qc)"       testDAccumHold2
    ]
  
  -- * Wave-form generation
  
  wfg_t0 :: [Double]
  wfg_t0 = take 16 $ embed (hold 99.99) wfg_inp1
  
  wfg_t0r =
    [ 99.99, 99.99, 1.0, 1.0
    , 2.0,   2.0,   2.0, 2.0
    , 3.0,   4.0,   4.0, 4.0
    , 0.0,   0.0,   0.0, 0.0
    ]
  
  wfg_inp1 = deltaEncode 1.0 $
    [ NoEvent,   NoEvent,   Event 1.0, NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 4.0, Event 4.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  wfg_t1 :: [Double]
  wfg_t1 = take 16 $ embed (hold 99.99) wfg_inp2
  
  wfg_t1r =
    [ 1.0, 1.0, 1.0, 1.0
    , 2.0, 2.0, 2.0, 2.0
    , 3.0, 4.0, 4.0, 4.0
    , 0.0, 0.0, 0.0, 0.0
    ]
  
  utils_inp1 = deltaEncode 1.0 $
    [ NoEvent,   NoEvent,   Event 1.0, NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 4.0, Event 4.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  utils_inp2 = deltaEncode 1.0 $
    [ Event 1.0, NoEvent,   NoEvent,   NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 4.0, Event 4.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  utils_t0 :: [Double]
  utils_t0 = take 16 $ embed (dHold 99.99) utils_inp1
  
  utils_t0r =
    [ 99.99, 99.99, 99.99, 1.0
    , 1.0,   2.0,   2.0,   2.0
    , 2.0,   3.0,   4.0,   4.0
    , 4.0,   0.0,   0.0,   0.0
    ]
  
  utils_t1 :: [Double]
  utils_t1 = take 16 $ embed (dHold 99.99) utils_inp2
  
  utils_t1r =
    [ 99.99, 1.0, 1.0, 1.0
    , 1.0,   2.0, 2.0, 2.0
    , 2.0,   3.0, 4.0, 4.0
    , 4.0,   0.0, 0.0, 0.0
    ]
  
  wfg_inp2 = deltaEncode 1.0 $
    [ Event 1.0, NoEvent,   NoEvent,   NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 4.0, Event 4.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  wfg_t2 :: [Double]
  wfg_t2 = take 25 $ embed (trackAndHold 99.99) wfg_inp3
  
  wfg_t2r =
    [ 99.99, 99.99, 1.0, 2.0, 3.0
    , 4.0,   4.0,   4.0, 4.0, 3.0
    , 2.0,   2.0,   1.0, 0.0, 1.0
    , 2.0,   3.0,   3.0, 3.0, 4.0
    , 4.0,   4.0,   4.0, 4.0, 4.0
    ]
  
  wfg_inp3 = deltaEncode 1.0 $
    [ Nothing,  Nothing,  Just 1.0, Just 2.0, Just 3.0
    , Just 4.0, Nothing,  Nothing,  Nothing,  Just 3.0
    , Just 2.0, Nothing,  Just 1.0, Just 0.0, Just 1.0
    , Just 2.0, Just 3.0, Nothing,  Nothing,  Just 4.0
    ]
    ++ repeat Nothing
  
  wfg_t3 :: [Double]
  wfg_t3 = take 25 $ embed (trackAndHold 99.99) wfg_inp4
  
  wfg_t3r =
    [ 0.0, 0.0, 1.0, 2.0, 3.0
    , 4.0, 4.0, 4.0, 4.0, 3.0
    , 2.0, 2.0, 1.0, 0.0, 1.0
    , 2.0, 3.0, 3.0, 3.0, 4.0
    , 4.0, 4.0, 4.0, 4.0, 4.0
    ]
  
  wfg_inp4 = deltaEncode 1.0 $
    [ Just 0.0, Nothing,  Just 1.0, Just 2.0, Just 3.0
    , Just 4.0, Nothing,  Nothing,  Nothing,  Just 3.0
    , Just 2.0, Nothing,  Just 1.0, Just 0.0, Just 1.0
    , Just 2.0, Just 3.0, Nothing,  Nothing,  Just 4.0
    ]
    ++ repeat Nothing
  
  utils_t2 :: [Double]
  utils_t2 = take 25 $ embed (dTrackAndHold 99.99) utils_inp3
  
  utils_t2r =
    [ 99.99, 99.99, 99.99, 1.0, 2.0
    , 3.0,   4.0,   4.0,   4.0, 4.0
    , 3.0,   2.0,   2.0,   1.0, 0.0
    , 1.0,   2.0,   3.0,   3.0, 3.0
    , 4.0,   4.0,   4.0,   4.0, 4.0
    ]
  
  utils_inp3 = deltaEncode 1.0 $
    [ Nothing,  Nothing,  Just 1.0, Just 2.0, Just 3.0
    , Just 4.0, Nothing,  Nothing,  Nothing,  Just 3.0
    , Just 2.0, Nothing,  Just 1.0, Just 0.0, Just 1.0
    , Just 2.0, Just 3.0, Nothing,  Nothing,  Just 4.0
    ]
    ++ repeat Nothing
  
  utils_t3 :: [Double]
  utils_t3 = take 25 $ embed (dTrackAndHold 99.99) utils_inp4
  
  utils_t3r =
    [ 99.99, 0.0, 0.0, 1.0, 2.0
    , 3.0,   4.0, 4.0, 4.0, 4.0
    , 3.0,   2.0, 2.0, 1.0, 0.0
    , 1.0,   2.0, 3.0, 3.0, 3.0
    , 4.0,   4.0, 4.0, 4.0, 4.0
    ]
  
  utils_inp4 = deltaEncode 1.0 $
    [ Just 0.0, Nothing,  Just 1.0, Just 2.0, Just 3.0
    , Just 4.0, Nothing,  Nothing,  Nothing,  Just 3.0
    , Just 2.0, Nothing,  Just 1.0, Just 0.0, Just 1.0
    , Just 2.0, Just 3.0, Nothing,  Nothing,  Just 4.0
    ]
    ++ repeat Nothing
  
  -- * Accumulators
  
  accum_t0 :: [Event Double]
  accum_t0 = take 16 $ embed (accum 0.0) accum_inp1
  
  accum_t0r =
    [ NoEvent,   NoEvent,    Event 1.0,  NoEvent
    , Event 3.0, NoEvent,    NoEvent,    NoEvent
    , Event 9.0, Event 14.0, Event 19.0, NoEvent
    , Event 9.5, NoEvent,    NoEvent,    NoEvent
    ]
  
  accum_inp1 = (fromJust (head delta_inp), zip (repeat 1.0) (tail delta_inp))
    where
      delta_inp =
        [ Just NoEvent, Nothing, Just (Event (+1.0)), Just NoEvent
        , Just (Event (+2.0)), Just NoEvent, Nothing, Nothing
        , Just (Event (*3.0)), Just (Event (+5.0)), Nothing, Just NoEvent
        , Just (Event (/2.0)), Just NoEvent, Nothing, Nothing
        ]
        ++ repeat Nothing
  
  accum_t1 :: [Event Double]
  accum_t1 = take 16 $ embed (accum 0.0) accum_inp2
  
  accum_t1r =
    [ Event 1.0, NoEvent,    NoEvent,    NoEvent
    , Event 3.0, NoEvent,    NoEvent,    NoEvent
    , Event 9.0, Event 14.0, Event 19.0, NoEvent
    , Event 9.5, NoEvent,    NoEvent,    NoEvent
    ]
  
  accum_inp2 = (fromJust (head delta_inp), zip (repeat 1.0) (tail delta_inp))
    where
      delta_inp =
        [ Just (Event (+1.0)), Just NoEvent, Nothing, Nothing
        , Just (Event (+2.0)), Just NoEvent, Nothing, Nothing
        , Just (Event (*3.0)), Just (Event (+5.0)), Nothing, Just NoEvent
        , Just (Event (/2.0)), Just NoEvent, Nothing, Nothing
        ]
        ++ repeat Nothing
  
  accum_t2 :: [Event Int]
  accum_t2 = take 16 $ embed (accumBy (\a d -> a + floor d) 0) accum_inp3
  
  accum_t2r :: [Event Int]
  accum_t2r =
    [ NoEvent,  NoEvent,  Event 1,  NoEvent
    , Event 3,  NoEvent,  NoEvent,  NoEvent
    , Event 6,  Event 11, Event 16, NoEvent
    , Event 16, NoEvent,  NoEvent,  NoEvent
    ]
  
  accum_inp3 = deltaEncode 1.0 $
    [ NoEvent,   NoEvent,   Event 1.0, NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 5.0, Event 5.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  accum_t3 :: [Event Int]
  accum_t3 = take 16 $ embed (accumBy (\a d -> a + floor d) 0) accum_inp4
  
  accum_t3r :: [Event Int]
  accum_t3r =
    [ Event 1,  NoEvent,  NoEvent,  NoEvent
    , Event 3,  NoEvent,  NoEvent,  NoEvent
    , Event 6,  Event 11, Event 16, NoEvent
    , Event 16, NoEvent,  NoEvent,  NoEvent
    ]
  
  accum_inp4 = deltaEncode 1.0 $
    [ Event 1.0, NoEvent,   NoEvent,   NoEvent
    , Event 2.0, NoEvent,   NoEvent,   NoEvent
    , Event 3.0, Event 5.0, Event 5.0, NoEvent
    , Event 0.0, NoEvent,   NoEvent,   NoEvent
    ]
    ++ repeat NoEvent
  
  accum_t8 :: [Event Int]
  accum_t8 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumBy (+) 0
                              >>> accumBy (+) 0)
                             accum_inp5
  
  accum_t8r :: [Event Int]
  accum_t8r = [ NoEvent,  NoEvent, NoEvent, NoEvent
              , Event 1,  NoEvent, NoEvent, NoEvent
              , Event 3,  NoEvent, NoEvent, NoEvent
              , Event 6,  NoEvent, NoEvent, NoEvent
              , Event 10, NoEvent, NoEvent, NoEvent
              , Event 15, NoEvent, NoEvent, NoEvent
              , Event 21, NoEvent, NoEvent, NoEvent
              , Event 28, NoEvent, NoEvent, NoEvent
              , Event 36, NoEvent, NoEvent, NoEvent
              , Event 45, NoEvent, NoEvent, NoEvent
              ]
  
  accum_inp5 = deltaEncode 0.25 (repeat ())
  
  accum_t9 :: [Int]
  accum_t9 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumBy (+) 0
                              >>> accumBy (+) 0
                              >>> hold 0)
                             accum_inp5
  
  accum_t9r :: [Int]
  accum_t9r = [ 0,0,0,0,1,1,1,1,3,3,3,3,6,6,6,6,10,10,10,10,15,15,15,15
              , 21,21,21,21,28,28,28,28,36,36,36,36,45,45,45,45
              ]
  
  accum_t11 :: [Int]
  accum_t11 = take 40 $ embed (repeatedly 1.0 1
                               >>> accumBy (+) 0
                               >>> accumBy (+) 0
                               >>> dHold 0)
                              accum_inp5
  
  accum_t11r :: [Int]
  accum_t11r = [ 0,0,0,0,0,1,1,1,1,3,3,3,3,6,6,6,6,10,10,10,10,15,15,15
               , 15,21,21,21,21,28,28,28,28,36,36,36,36,45,45,45
               ]
  
  accum_t10 :: [Int]
  accum_t10 = take 40 $ embed (repeatedly 1.0 1
                               >>> accumBy (+) 0
                               >>> accumHoldBy (+) 0)
                              accum_inp5
  
  accum_t10r :: [Int]
  accum_t10r = accum_t9 -- Should agree!
  
  accum_t12 :: [Int]
  accum_t12 = take 40 $ embed (repeatedly 1.0 1
                               >>> accumBy (+) 0
                               >>> dAccumHoldBy (+) 0)
                              accum_inp5
  
  accum_t12r :: [Int]
  accum_t12r = accum_t11 -- Should agree!
  
  accum_t4 :: [Event (Bool,Int)]
  accum_t4 = take 16 $ embed (accumFilter accum_accFiltFun1 0) accum_inp3
  
  accum_t4r :: [Event (Bool,Int)]
  accum_t4r =
    [ NoEvent,         NoEvent, NoEvent,         NoEvent
    , NoEvent,         NoEvent, NoEvent,         NoEvent
    , Event (False,6), NoEvent, Event (True,16), NoEvent
    , Event (True,16), NoEvent, NoEvent,         NoEvent
    ]
  
  accum_accFiltFun1 a d =
    let a' = a + floor d
    in if even a'
         then (a', Just (a' > 10, a'))
         else (a', Nothing)
  
  accum_t5 :: [Event (Bool,Int)]
  accum_t5 = take 16 $ embed (accumFilter accum_accFiltFun2 0) accum_inp4
  
  accum_t5r :: [Event (Bool,Int)]
  accum_t5r =
    [ Event (False,1), NoEvent,         NoEvent, NoEvent
    , Event (False,3), NoEvent,         NoEvent, NoEvent
    , NoEvent,         Event (True,11), NoEvent, NoEvent
    , NoEvent,         NoEvent,         NoEvent, NoEvent
    ]
  
  accum_accFiltFun2 a d =
    let a' = a + floor d
    in if odd a'
         then (a', Just (a' > 10, a'))
         else (a', Nothing)
  
  -- This can be seen as the definition of accumFilter
  accumFilter2 :: (c -> a -> (c, Maybe b)) -> c -> SF (Event a) (Event b)
  accumFilter2 f c_init =
      switch (never &&& attach c_init) afAux
    where
      afAux (c, a) =
        case f c a of
          (c', Nothing) -> switch (never &&& (notYet>>>attach c')) afAux
          (c', Just b)  -> switch (now b &&& (notYet>>>attach c')) afAux
  
      attach :: b -> SF (Event a) (Event (b, a))
      attach c = arr (fmap (\a -> (c, a)))
  
  accum_t6 :: [Event (Bool,Int)]
  accum_t6 = take 16 $ embed (accumFilter2 accum_accFiltFun1 0) accum_inp3
  
  accum_t6r = accum_t4 -- Should agree!
  
  accum_t7 :: [Event (Bool,Int)]
  accum_t7 = take 16 $ embed (accumFilter2 accum_accFiltFun2 0) accum_inp4
  
  accum_t7r = accum_t5 -- Should agree!
  
  accum_accFiltFun3 :: Int -> Int -> (Int, Maybe Int)
  accum_accFiltFun3 s a =
    let s' = s + a
    in if odd s'
         then (s', Just s')
         else (s', Nothing)
  
  accum_t13 :: [Event Int]
  accum_t13 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumFilter accum_accFiltFun3 0
                              >>> accumBy (+) 0
                              >>> accumBy (+) 0)
                              accum_inp5
  
  accum_t13r :: [Event Int]
  accum_t13r = [ NoEvent,  NoEvent, NoEvent, NoEvent
               , Event 1,  NoEvent, NoEvent, NoEvent
               , NoEvent,  NoEvent, NoEvent, NoEvent
               , Event 5,  NoEvent, NoEvent, NoEvent
               , NoEvent,  NoEvent, NoEvent, NoEvent
               , Event 14, NoEvent, NoEvent, NoEvent
               , NoEvent,  NoEvent, NoEvent, NoEvent
               , Event 30, NoEvent, NoEvent, NoEvent
               , NoEvent,  NoEvent, NoEvent, NoEvent
               , Event 55, NoEvent, NoEvent, NoEvent
               ]
  
  accum_t14 :: [Int]
  accum_t14 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumFilter accum_accFiltFun3 0
                              >>> accumBy (+) 0
                              >>> accumBy (+) 0
                              >>> hold 0)
                              accum_inp5
  
  accum_t14r :: [Int]
  accum_t14r = [ 0,0,0,0,1,1,1,1,1,1,1,1,5,5,5,5,5,5,5,5,14,14,14,14
               , 14,14,14,14,30,30,30,30,30,30,30,30,55,55,55,55
               ]
  
  accum_t15 :: [Int]
  accum_t15 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumFilter accum_accFiltFun3 0
                              >>> accumBy (+) 0
                              >>> accumHoldBy (+) 0)
                              accum_inp5
  
  accum_t15r :: [Int]
  accum_t15r = accum_t14 -- Should agree!
  
  accum_t16 :: [Int]
  accum_t16 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumFilter accum_accFiltFun3 0
                              >>> accumBy (+) 0
                              >>> accumBy (+) 0
                              >>> dHold 0)
                              accum_inp5
  
  accum_t16r :: [Int]
  accum_t16r = [ 0,0,0,0,0,1,1,1,1,1,1,1,1,5,5,5,5,5,5,5,5,14,14,14
               , 14,14,14,14,14,30,30,30,30,30,30,30,30,55,55,55
               ]
  
  accum_t17 :: [Int]
  accum_t17 = take 40 $ embed (repeatedly 1.0 1
                              >>> accumFilter accum_accFiltFun3 0
                              >>> accumBy (+) 0
                              >>> dAccumHoldBy (+) 0)
                              accum_inp5
  
  accum_t17r :: [Int]
  accum_t17r = accum_t16 -- Should agree!
  
  testAccumHold1 :: Property
  testAccumHold1 =
      forAll arbitrary $ \x ->
      forAll myStream  $
        evalT $
          Always $ prop ((sf x &&& sfByHand x), const close)
  
    where
      myStream :: Gen (SignalSampleStream ())
      myStream = uniDistStream
  
      sf :: Double -> SF () Double
      sf x = never >>> accumHold x
  
      sfByHand :: Double -> SF () Double
      sfByHand x = constant x
  
      close (x, y) = abs (x - y) < 0.05
  
  testAccumHold2 :: Property
  testAccumHold2 =
      forAll myStream  $ evalT $
        Always $ prop ((sf &&& sfByHand), const close)
  
    where
      myStream :: Gen (SignalSampleStream ())
      myStream = uniDistStreamMaxDT maxSamplingDelay
  
      sf :: SF () Double
      sf = repeatedly eventUpdateDelay (+1) >>> accumHold 0
  
      sfByHand :: SF () Double
      sfByHand = time >>> arr floorDouble
  
      floorDouble :: Double -> Double
      floorDouble x = fromIntegral ((floor x) :: Integer)
  
      -- Important that this number be smaller than eventUpdateDelay below.
      maxSamplingDelay :: DTime
      maxSamplingDelay = 0.9
  
      -- Important that this delay be greater than maxSamplingDelay above.
      eventUpdateDelay :: DTime
      eventUpdateDelay = 1.0
  
      close (x, y) = abs (x - y) < 0.05
  
  -- Test that dAccumHold is like constant for the first sample
  testDAccumHold1 :: Property
  testDAccumHold1 =
      forAll arbitrary $ \x ->
      forAll myStream  $
        evalT $
          prop (arr (fmap apply) >>> (sf x &&& sfByHand x), const close)
  
    where
      myStream :: Gen (SignalSampleStream (Event (Fun Double Double)))
      myStream = uniDistStream
  
      sf :: Double -> SF (Event (Double -> Double)) Double
      sf x = dAccumHold x
  
      sfByHand :: Double -> SF (Event (Double -> Double)) Double
      sfByHand x = constant x
  
      close (x, y) = abs (x - y) < 0.05
  
  -- Test that dAccumHold is like accumHold delayed
  testDAccumHold2 :: Property
  testDAccumHold2 =
      forAll arbitrary $ \x ->
      forAll myStream  $
        evalT $
          Next $ Always $
            prop (arr (fmap apply) >>> (sf x &&& sfByHand x), const close)
  
    where
      myStream :: Gen (SignalSampleStream (Event (Fun Double Double)))
      myStream = uniDistStream
  
      sf :: Double -> SF (Event (Double -> Double)) Double
      sf x = dAccumHold x
  
      sfByHand :: Double -> SF (Event (Double -> Double)) Double
      sfByHand x = accumHold x >>> loopPre x (arr swap)
  
      close (x, y) = abs (x - y) < 0.05
  
  -- * Auxiliary
  -- prop :: SF a b -> (a -> b ->
  prop (a,b) = SP ((identity &&& a) >>^ uncurry b)
  
  -- ** Arbitrary value generation
  instance Arbitrary x => Arbitrary (Event x) where
    arbitrary = oneof [ return NoEvent, fmap Event $ arbitrary ]
  