├── CHANGELOG.md ├── observable.cabal ├── README.md ├── LICENSE └── Control └── Observable.hs /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # 0.1.1 2 | * Rename Mock to Capture 3 | * Add `(*=>)` combinator 4 | * Add heartbeat usage example 5 | * Add `bypass` combinator 6 | 7 | # 0.1.2 8 | * Add `uprise` combinator 9 | * Remove `bypass` and `(*=>)` 10 | 11 | # 0.1.3 12 | * Add `watch` combinator 13 | * Rename `uprise` to `chase` 14 | * Add four infix combinators 15 | 16 | # 0.1.4 17 | * Remove `Usage.Heartbeat` module 18 | 19 | # 0.1.5 20 | * Rename `chase` to `follow` 21 | -------------------------------------------------------------------------------- /observable.cabal: -------------------------------------------------------------------------------- 1 | name: observable 2 | version: 0.1.5 3 | synopsis: Continuation patterns 4 | description: Make your actions to be observable and handle events from them. 5 | homepage: https://github.com/iokasimov/observable 6 | bug-reports: https://github.com/iokasimov/observable/issues 7 | extra-source-files: CHANGELOG.md 8 | license: BSD3 9 | license-file: LICENSE 10 | author: Murat Kasimov 11 | maintainer: Murat Kasimov 12 | copyright: Copyright (c) 2018 Murat Kasimov 13 | category: Control 14 | build-type: Simple 15 | cabal-version: >= 1.10 16 | 17 | source-repository head 18 | type: git 19 | location: https://github.com/iokasimov/observable.git 20 | 21 | library 22 | exposed-modules: Control.Observable 23 | build-depends: base == 4.*, transformers 24 | default-language: Haskell2010 25 | default-extensions: NoImplicitPrelude, PackageImports 26 | ghc-options: -fno-warn-tabs 27 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Make your actions to be observable and listen to events from them, algebraically. 2 | 3 | Let's imagine simple example: we want to listen to STDIN. We have `getLine` function that can capture a list of ASCII-symbols - all we need is to make this action to be observable. 4 | 5 | ```haskell 6 | obs :: Monad f => f a -> Observable f a r 7 | ``` 8 | Good, now we want to subscribe on events and set up callback, if we want to listen to events forever, we need `subscribe` function: 9 | 10 | ```haskell 11 | subscribe :: Applicative f => Observable f a r -> (a -> f r) -> f r 12 | ``` 13 | 14 | First, we make action to be observable, then set up callback and at the end, subscribe on events: 15 | 16 | ```haskell 17 | subscribe (obs getLine) handler 18 | ``` 19 | 20 | Our handler will count amount of characters in strings and send this into STDOUT: 21 | 22 | ```haskell 23 | handler = print . (<>) "Length of string was: " . show . length 24 | ``` 25 | 26 | Let's try it out: 27 | ```haskell 28 | > hello, my dear friend! 29 | > "Length of string was: 22" 30 | ``` 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | BSD 3-Clause License 2 | 3 | Copyright (c) 2018, Murat Kasimov 4 | All rights reserved. 5 | 6 | Redistribution and use in source and binary forms, with or without 7 | modification, are permitted provided that the following conditions are met: 8 | 9 | * Redistributions of source code must retain the above copyright notice, this 10 | list of conditions and the following disclaimer. 11 | 12 | * Redistributions in binary form must reproduce the above copyright notice, 13 | this list of conditions and the following disclaimer in the documentation 14 | and/or other materials provided with the distribution. 15 | 16 | * Neither the name of the copyright holder nor the names of its 17 | contributors may be used to endorse or promote products derived from 18 | this software without specific prior written permission. 19 | 20 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 21 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 22 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 23 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 24 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 25 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 26 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 27 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 28 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 29 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 30 | -------------------------------------------------------------------------------- /Control/Observable.hs: -------------------------------------------------------------------------------- 1 | module Control.Observable 2 | ( Observable, dispatch, obs 3 | , notify, follow, subscribe, watch 4 | , (.:~.), (.:~*), (*:~.), (*:~*) 5 | ) where 6 | 7 | import "base" Control.Applicative (Applicative (pure)) 8 | import "base" Control.Monad (Monad, forever) 9 | import "base" Data.Function (($), (.), flip) 10 | import "base" Data.Traversable (Traversable (traverse)) 11 | import "transformers" Control.Monad.Trans.Cont (ContT (..)) 12 | import "transformers" Control.Monad.Trans.Class (lift) 13 | 14 | newtype Capture r f a = Capture { captured :: f r } 15 | 16 | -- | Capture used here as limiter of continuation 17 | type Observable f a r = ContT r (Capture r f) a 18 | 19 | -- | Make continuation to be observable 20 | dispatch :: ContT r f a -> Observable f a r 21 | dispatch f = ContT $ \h -> Capture $ runContT f (captured . h) 22 | 23 | -- | Make monadic action to be observable 24 | obs :: Monad f => f a -> Observable f a r 25 | obs action = dispatch $ lift action 26 | 27 | -- | Listen only first event, call back just once 28 | notify :: Observable f a r -> (a -> f r) -> f r 29 | notify r f = captured $ runContT r (Capture . f) 30 | 31 | -- | Infix version of 'notify' 32 | (.:~.) :: Observable f a r -> (a -> f r) -> f r 33 | (.:~.) = notify 34 | 35 | -- | Listen only first event, call back forever 36 | follow :: Applicative f => Observable f a r -> (a -> f r) -> f r 37 | follow r f = captured $ runContT r (Capture . forever . f) 38 | 39 | -- | Infix version of 'follow' 40 | (.:~*) :: Applicative f => Observable f a r -> (a -> f r) -> f r 41 | (.:~*) = follow 42 | 43 | -- | Listen all events from action, call back just once 44 | subscribe :: Applicative f => Observable f a r -> (a -> f r) -> f r 45 | subscribe r f = forever $ captured $ runContT r (Capture . f) 46 | 47 | -- | Infix version of 'subscribe' 48 | (*:~.) :: Applicative f => Observable f a r -> (a -> f r) -> f r 49 | (*:~.) = subscribe 50 | 51 | -- | Listen all events from action, call back forever 52 | watch :: Applicative f => Observable f a r -> (a -> f r) -> f r 53 | watch r f = forever $ captured $ runContT r (Capture . forever . f) 54 | 55 | -- | Infix version of 'watch' 56 | (*:~*) :: Applicative f => Observable f a r -> (a -> f r) -> f r 57 | (*:~*) = watch 58 | --------------------------------------------------------------------------------