Tom Murphy
FARM 2016
And we'd like to be able to hotswap
Experiment, tweak params, add stuff and throw stuff away, freeze/hold, ...
fmap :: Functor f => (a -> b) -> f a -> f b
chars = "midair" :: [Char]
fmap (=='a') chars :: [Bool]
getChar :: IO Char
fmap (=='a') getChar :: IO Bool
Pseudocode:
lastKeyPressed :: FRP Char
fmap (=='a') lastKeyPressed :: FRP Bool
data SFlow a b
Discrete time
E-FRP, RT-FRP
data SFlow a b
sMap :: (a -> b) -> SFlow a b
sFold :: c -> (a -> c -> c) -> SFlow a c
sFilter :: (b -> Bool) -> SFlow b b
sCompose :: SFlow b c -> SFlow a b -> SFlow a c
-- ...
instance Applicative (SFlow a)
instance Arrow SFlow
sFold True (\_ -> not) <<< sFilter (==' ')
:: SFlow Char Bool
(demo!)
width :: SFlow x Double
height :: SFlow x Double
area :: SFlow x Double
area = (*) <$> width <*> height
-- Note it's deterministic
do w <- width
h <- height
pure (w * h)
Including any sub-component
data Direction = Up | Down
volChange :: Double -> Direction -> Double
volChange v Up = v * 1.01
volChange v Down = v * 0.99
buttonHistory :: [Direction] -- 100 up, 50 down
foldl volChange 1 buttonHistory == 1.65...
volChange' v Down = v * 0.97
volChange' v Up = v * 1.03
foldl volChange' 1 buttonHistory == 4.19...
"Compiled code is roughly 10x faster than interpreted code, but takes about 2x longer to produce (perhaps longer if optimisation is on)." - From the GHC Users' Manual
(Simplified:)
data SFlow a c where
SF_Map :: (a -> c) -> SFlow a c
SF_Compose
:: forall a b c.
SFlow b c -> SFlow a b -> SFlow a c
SF_Fold :: c -> (a -> c -> c) -> SFlow a c
SF_Filter
:: Maybe a -> (a -> Bool) -> SFlow a a
SF_NodeRef
:: TVar (Maybe a) -> TVar (Maybe c)
-> TVar (SFlow a c) -> SFlow a c
SF_NodeRef
soonfireGraph :: TVar (SFlow i o) -> i -> STM (Maybe o)
STM
for graph updateMaybe
for sFilter
mkNodeRef :: SFlow a b -> IO (SFNodeRef a b)
nRef :: SFNodeRef a b -> SFlow a b
hotswap :: SFNodeRef a b
-> (Maybe a -> Maybe b -> SFlow a b)
-> IO ()
fmap nRef (mkNodeRef x)
/=
pure x
ref <- mkNodeRef $ sMap (transpose 2)
:: IO (SFNodeRef Chord Chord)
sMap playChord <<< nRef ref <<< sMap makeChord
:: SFlow Note (IO ())
hotswap ref $ \_ _ -> sMap (transpose 4)
ref <- mkNodeRef $ sFold 1 volChange
hotswap ref $ \_ lastOut ->
sFold (fromMaybe 1 lastOut) volChange'
hotswap ref $ \_ (Just lastOut) ->
sFold lastOut volChange'
hotswapFold ref 1 volChange'
-- | Gives the first item that passes
sFind :: (a -> Bool) -> SFlow a a
sTake :: Int -> SFlow a [a]
sAll :: (a -> Bool) -> SFlow a Bool
sElem :: Eq a => a -> SFlow a Bool
sMapMaybe :: (a -> Maybe b) -> SFlow a b
sRights :: SFlow (Either l r) r
sPrint :: Show a => SFlow a (Fx ())
sSum :: Num n => SFlow n n
sMax :: Ord a => SFlow a a
hotswapHold :: SFNodeRef x a -> IO ()
hotswapFold :: SFNodeRef a c -> c -> (a -> c -> c) -> IO ()
hotswapCount :: SFNodeRef x n -> IO ()
-- ...
We can demonstrate its use using sCount
:
sCount :: Num n => SFlow x n
== sSum <<< sConst 1
== sFold (+) 0 <<< sMap (const 1)
We can, given keyboard input, count every key press of a digit character:
ref <- mkNodeRef sCount
nRef ref <<< sFilter isDigit
:: SFlow Char Int
Then stop counting for a while:
hotswapHold ref
Then, after more time, start counting where we left off:
hotswapCount ref
Even after switching back to counting fires with "count," none of the key presses that occurred while the "hold" was in effect are part of the sum emitted from the graph.