Last active
August 29, 2015 13:57
-
-
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).
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
{-# 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