Skip to content

Instantly share code, notes, and snippets.

@Heimdell
Last active August 29, 2015 13:57
Show Gist options
  • Save Heimdell/9675964 to your computer and use it in GitHub Desktop.
Save Heimdell/9675964 to your computer and use it in GitHub Desktop.
How to understand X? Make it yourself. So, this is my understanding of Functional Reactive Programming (powered by arrows).
module Behaviour where
import Prelude hiding (id, (.), head, tail, zip, unzip, map, repeat, cycle)
import Control.Applicative
import Control.Arrow
import Control.Category
import Data.Stream
newtype Behaviour a b =
Behaviour { runBehaviour :: a -> (Behaviour a b, b) }
instance Category Behaviour where
id = arr id
f . g = Behaviour $ \a ->
let
(g', b) = g `runBehaviour` a
(f', c) = f `runBehaviour` b
in (f' . g', c)
instance Arrow Behaviour where
arr pure = Behaviour $ const (arr pure) &&& pure
first arrow =
Behaviour $ \(a, b) ->
let
(arrow', c) = arrow `runBehaviour` a
in (first arrow', (c, b))
instance ArrowLoop Behaviour where
loop arrow =
Behaviour $ \a ->
let
(arrow', (b, s)) = arrow `runBehaviour` (a, s)
in (loop arrow', b)
instance Num b => Num (Behaviour a b) where
fromInteger = constant . fromInteger
(+) = liftB2 (+)
(*) = liftB2 (*)
abs = arr abs
negate = arr negate
signum = arr signum
instance Functor (Behaviour a) where
fmap f = (>>> arr f)
instance Applicative (Behaviour a) where
pure = constant
f <*> x = Behaviour $ \a ->
let
(f', pf) = f `runBehaviour` a
(x', px) = x `runBehaviour` a
in (f' <*> x', pf px)
-- Seems to be no way to implement ArrowApply and Monad right.
-- The monad instance requires us in `ma >>= amb` transform the
-- result of `amb a` and send it as state to the future. But, the result
-- depends on arbitrary `a`, which makes it inaccessible.
-- `ArrowApply` due to semantics `being a monad` shows the same problem.
instance ArrowApply Behaviour where
app =
Behaviour $ \(bf, a) ->
let
(_, c) = bf `runBehaviour` a
in (app, c)
liftB2 (?) left right = (?) <$> left <*> right
delay :: a -> Behaviour a a
delay x =
Behaviour $ \a ->
(delay a, x)
onNothing :: a -> Behaviour (Maybe a) a
onNothing filler =
Behaviour $ \a ->
case a of
Just value -> (onNothing filler, value)
Nothing -> (onNothing filler, filler)
hold :: a -> Behaviour (Maybe a) a
hold seed =
Behaviour $ \a ->
case a of
Just value -> (hold value, value)
Nothing -> (hold seed, seed)
peak :: Eq a => a -> Behaviour a (Maybe a)
peak prev =
Behaviour $ \a ->
( peak a
, if a == prev
then Nothing
else Just a
)
conditions :: [(Behaviour a Bool, Behaviour a b)] -> Behaviour a b
conditions [] = error "No condition matched, use fallback (constant True) as the last one!"
conditions list =
conditions' [] list
where
conditions' failed ((predicate, action) : rest) =
Behaviour $ \a ->
let
(predicate', flag) = predicate `runBehaviour` a
in if flag
then let
(action', b) = action `runBehaviour` a
in (conditions $ reverse failed ++ (predicate', action') : rest, b)
else
let
next = conditions' ((predicate', action) : failed) rest
in next `runBehaviour` a
andB, orB :: Behaviour a Bool -> Behaviour a Bool -> Behaviour a Bool
andB = liftB2 (&&)
orB = liftB2 (||)
conditions_test = conditions
[ arr (> 5) ==> constant "over five"
, arr (<= 5) `andB` arr (> 2) ==> arr show >>> arr ("is " ++)
, arr (== 1) ==> integrate 0 (+)
>>> arr show
>>> arr (++ " time we found 1")
, fallback ==> constant "very bad"
]
infix 0 ==>
(==>) = (,)
fallback = constant True
whenB :: Behaviour a b -> Behaviour a Bool -> Behaviour a (Maybe b)
action `whenB` predicate =
Behaviour $ \a ->
let
(predicate', flag) = predicate `runBehaviour` a
in if flag
then let
(action', b) = action `runBehaviour` a
in (action' `whenB` predicate', Just b)
else (action `whenB` predicate', Nothing)
loop' seed arrow =
loop $ second (delay seed) >>> arrow
fib = integrate (0, 1) (\(a, b) _ -> (a + b, a)) >>> arr snd
nat = 1 >>> integrate 0 (+)
tick = nat
infix 0 `behave`
behave :: Behaviour a b -> (Stream a) -> (Stream b)
behave b (a `Cons` as) =
let
(b', c) = b `runBehaviour` a
in c <:> behave b' as
constant :: b -> Behaviour a b
constant x = Behaviour $ const $ (constant x, x)
diff init (?) = delay init &&& id >>> arr (uncurry (?))
fromAngle = arr sin &&& arr cos :: Behaviour Float (Float, Float)
integrate :: c -> (c -> b -> c) -> Behaviour b c
integrate seed (?) =
Behaviour $ \a ->
let
newseed = seed ? a
in (integrate newseed (?), newseed)
module Gaem where
import Prelude hiding (id, head, tail, zip, unzip, map, repeat, cycle)
import Behaviour
import Control.Arrow
import Control.Category
import Data.Stream
import System.IO.Unsafe
import System.Timeout
data Gaem = Gaem Position
type Position = (Int, Int)
type Velocity = (Int, Int)
keyToWalkDirection :: Behaviour Char (Int, Int)
keyToWalkDirection = conditions
[ arr (== 'w') ==> constant ( 0, 1)
, arr (== 's') ==> constant ( 0, -1)
, arr (== 'a') ==> constant (-1, 0)
, arr (== 'd') ==> constant ( 1, 0)
, fallback ==> constant ( 0, 0)
]
move :: Position -> Velocity -> Position
move (x, y) (dx, dy) = (x + dx, y + dy)
game :: Position -> Behaviour (Maybe Char) (Position, Integer)
game position =
onNothing '.' >>> keyToWalkDirection >>> (integrate position move &&& tick)
-- Tick emits counter++ (from 0) on any signal.
-- `&&&` sends incoming signal to both operands and packs results
-- into a tuple.
-- This function scans pressed keys at least 5 times per sec.
-- If a key is pressed, it emits `Just key`, otherwise `Nothing`.
-- It also is the solely source of time ticks.
input :: IO (Stream (Maybe Char))
input = do
char <- unsafeInterleaveIO $ timeout (200000) $ getChar
rest <- unsafeInterleaveIO input
return (char <:> rest)
output :: Show a => Stream a -> IO ()
output = toList >>> mapM_ print
main :: IO ()
main = do
-- this command doesn't consume ALL input, it creates a queue.
commands <- input
let snapshots = game (0, 0) `behave` commands
output snapshots
{-# LANGUAGE Arrows #-}
--module Streamer where
import Prelude hiding
( id
, (.)
)
import Control.Arrow
import Control.Applicative
import Control.Category
--import Data.Stream
import Data.Monoid
import Data.Maybe
import qualified Data.List as List
type Stream = []
data Streamer a b = Streamer
{ listen :: Stream a -> Stream b }
infix 0 `listen`
instance Category Streamer where
id = arr id
f . g = Streamer $ listen f . listen g
instance Arrow Streamer where
arr = Streamer . map
first arrow = Streamer $ \pairs ->
let
(bs, ds) = unzip pairs
cs = arrow `listen` bs
in zip cs ds
instance ArrowLoop Streamer where
loop arrow =
Streamer $ \as ->
let
(cs, ds) = unzip $ listen arrow $ unsafe_zip as ds
in cs
unsafe_zip ~(l : ls) ~(r : rs) = (l, r) : unsafe_zip ls rs
instance ArrowApply Streamer where
app =
Streamer $ \pairs ->
let
(arrows, xs) = unzip pairs
-- it seems to work, but is that right?
in head arrows `listen` xs
instance ArrowChoice Streamer where
left arrow =
Streamer $ \choices ->
let
side (Left _) = True
side (Right _) = False
sides = map side choices
fromLeft (Left x) = x
fromRight (Right x) = x
lefts = map fromLeft $ filter side choices
rights = map fromRight $ filter (not . side) choices
worked = arrow `listen` lefts
weave (s : rest) (l : ls) (r : rs)
| s = Left l : weave rest ls (r : rs)
| otherwise = Right r : weave rest (l : ls) rs
in weave sides worked rights
initBy :: a -> Streamer a a
initBy object =
Streamer $ \as -> object : as
loopWith :: a -> Streamer (b, a) (c, a) -> Streamer b c
loopWith object arrow = loop $
second (initBy object) >>> arrow
instance Functor (Streamer a) where
fmap f = (>>^ f)
instance Applicative (Streamer a) where
pure = constant
f <*> x = Streamer $ \as ->
let
fs = f `listen` as
xs = x `listen` as
in zipWith ($) fs xs
liftS2 (?) l r = (?) <$> l <*> r
instance Num b => Num (Streamer a b) where
fromInteger = constant . fromInteger
(+) = liftS2 (+)
(*) = liftS2 (*)
abs = arr abs
negate = arr negate
signum = arr signum
constant :: b -> Streamer a b
constant = arr . const
whenB :: Streamer a b -> Streamer a Bool -> Streamer a (Maybe b)
whenB action predicate =
Streamer $ \as ->
let
answers = predicate `listen` as
classified = answers `zip` as
good = map snd $ filter fst $ classified
bad = map snd $ filter (not . fst) $ classified
worked = action `listen` good
weave (True : answers) (l : ls) = Just l : weave answers ls
weave (False : answers) ls = Nothing : weave answers ls
in weave answers worked
switchB = foldl1 (liftS2 first')
--switchB :: Streamer a (Maybe b) -> Streamer a (Maybe b) -> Streamer a (Maybe b)
--switchB l r =
-- Streamer $ \as ->
-- let
-- ls = l `listen` as
-- rs = r `listen` as
-- -- !stream = transpose' answered
-- in zipWith first ls rs
first' (Just a) _ = Just a
first' Nothing b = b
transpose' :: [Stream a] -> Stream [a]
transpose' list = List.map head list : transpose' (List.map tail list)
ticks = 1 >>> fold (+) 0
is object = arr (== object)
fold (+) zero =
Streamer $ inner zero
where
inner zero (hd : rest) =
let
new_hd = zero + hd
in new_hd : inner new_hd rest
test n arrow = take n (arrow `listen` repeat ())
test_1 = proc int -> do
result <- fold (+) 1 -<< int
mult <- fold (*) 1 -<< result
returnA -<< mult
test_2 = switchB
[ constant ( 0, -1) `whenB` is 'w'
, constant (-1, 0) `whenB` is 'a'
-- , constant ( 1, 0) `whenB` is 'd'
, constant ( 0, 0) `whenB` constant True
]
>>> arr fromJust
test_3 = test_2 >>> fold (\(x, y) (dx, dy) -> (x + dx, y + dy)) (0, 0)
test_4 = loopWith 0 $ arr $ \(a, b) -> (a + b, b + 1)
main = do
print (test_3 `listen` cycle "was ")
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment