packages feed

Etage (empty) → 0.1

raw patch · 15 files changed

+1796/−0 lines, 15 filesdep +basedep +ghcdep +mtlsetup-changed

Dependencies added: base, ghc, mtl, operational, random, time, unix

Files

+ Etage.cabal view
@@ -0,0 +1,51 @@+Name:                Etage+Version:             0.1+Synopsis:            A general data-flow framework+Description:         A general data-flow framework featuring nondeterminism and neurological pseudo-terminology. It can be used for+                     example for data-flow computations or event propagation networks. It tries hard to aide type checking and to+                     allow proper initialization and cleanup so that interfaces to input and output devices (data or events producers or+                     consumers) can be made (so that created models\/systems\/networks can be used directly in real world applications, for+                     example robots).+                     .+                     Its main goal is to model complex neural networks with more biological realism. Namely that impulses do+                     take time to travel and neuron responses are also not instantaneous. And of course that neural systems are in+                     its base nondeterministic and that some level of determinism is build upon that. All this of course makes reasoning+                     about such networks even harder (impossible?).+                     .+                     This framework is in fact just a simple abstraction of Haskell threads and data passing between them through+                     channels with threads' initialization and cleanup wrapped into a Haskell type class.+                     .+                     Feel free to contribute or suggest additional features or (example) programs or to create interfaces to other modules.+License:             LGPL-3+License-file:        LICENSE+Author:              Mitar Milutinovic+Maintainer:          mitar.haskell@tnode.com+Copyright:           (c) 2010 Mitar Milutinovic+Category:            Control+Build-type:          Simple+Cabal-version:       >= 1.8+Stability:           experimental+Homepage:            http://mitar.tnode.com++Library+  Exposed-modules:     Control.Etage,+                       Control.Etage.Dump,+                       Control.Etage.Sequence,+                       Control.Etage.Worker,+                       Control.Etage.Timeout,+                       Control.Etage.Function,+                       Control.Etage.Fail+  Build-depends:       base >= 4.3 && < 5,+                       mtl >= 1.1 && < 3,+                       random > 1.0 && < 2,+                       unix >= 2.4 && < 3,+                       time >= 1.1 && < 2,+                       operational >= 0.2 && < 1,+                       ghc >= 7.0.2+  Other-modules:       Control.Etage.Internals,+                       Control.Etage.Externals,+                       Control.Etage.Propagate,+                       Control.Etage.Incubator,+                       Control.Etage.Chan+  HS-source-dirs:      lib+  GHC-options:         -Wall
+ LICENSE view
@@ -0,0 +1,165 @@+                  GNU LESSER GENERAL PUBLIC LICENSE+                       Version 3, 29 June 2007++ Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>+ Everyone is permitted to copy and distribute verbatim copies+ of this license document, but changing it is not allowed.+++  This version of the GNU Lesser General Public License incorporates+the terms and conditions of version 3 of the GNU General Public+License, supplemented by the additional permissions listed below.++  0. Additional Definitions. ++  As used herein, "this License" refers to version 3 of the GNU Lesser+General Public License, and the "GNU GPL" refers to version 3 of the GNU+General Public License.++  "The Library" refers to a covered work governed by this License,+other than an Application or a Combined Work as defined below.++  An "Application" is any work that makes use of an interface provided+by the Library, but which is not otherwise based on the Library.+Defining a subclass of a class defined by the Library is deemed a mode+of using an interface provided by the Library.++  A "Combined Work" is a work produced by combining or linking an+Application with the Library.  The particular version of the Library+with which the Combined Work was made is also called the "Linked+Version".++  The "Minimal Corresponding Source" for a Combined Work means the+Corresponding Source for the Combined Work, excluding any source code+for portions of the Combined Work that, considered in isolation, are+based on the Application, and not on the Linked Version.++  The "Corresponding Application Code" for a Combined Work means the+object code and/or source code for the Application, including any data+and utility programs needed for reproducing the Combined Work from the+Application, but excluding the System Libraries of the Combined Work.++  1. Exception to Section 3 of the GNU GPL.++  You may convey a covered work under sections 3 and 4 of this License+without being bound by section 3 of the GNU GPL.++  2. Conveying Modified Versions.++  If you modify a copy of the Library, and, in your modifications, a+facility refers to a function or data to be supplied by an Application+that uses the facility (other than as an argument passed when the+facility is invoked), then you may convey a copy of the modified+version:++   a) under this License, provided that you make a good faith effort to+   ensure that, in the event an Application does not supply the+   function or data, the facility still operates, and performs+   whatever part of its purpose remains meaningful, or++   b) under the GNU GPL, with none of the additional permissions of+   this License applicable to that copy.++  3. Object Code Incorporating Material from Library Header Files.++  The object code form of an Application may incorporate material from+a header file that is part of the Library.  You may convey such object+code under terms of your choice, provided that, if the incorporated+material is not limited to numerical parameters, data structure+layouts and accessors, or small macros, inline functions and templates+(ten or fewer lines in length), you do both of the following:++   a) Give prominent notice with each copy of the object code that the+   Library is used in it and that the Library and its use are+   covered by this License.++   b) Accompany the object code with a copy of the GNU GPL and this license+   document.++  4. Combined Works.++  You may convey a Combined Work under terms of your choice that,+taken together, effectively do not restrict modification of the+portions of the Library contained in the Combined Work and reverse+engineering for debugging such modifications, if you also do each of+the following:++   a) Give prominent notice with each copy of the Combined Work that+   the Library is used in it and that the Library and its use are+   covered by this License.++   b) Accompany the Combined Work with a copy of the GNU GPL and this license+   document.++   c) For a Combined Work that displays copyright notices during+   execution, include the copyright notice for the Library among+   these notices, as well as a reference directing the user to the+   copies of the GNU GPL and this license document.++   d) Do one of the following:++       0) Convey the Minimal Corresponding Source under the terms of this+       License, and the Corresponding Application Code in a form+       suitable for, and under terms that permit, the user to+       recombine or relink the Application with a modified version of+       the Linked Version to produce a modified Combined Work, in the+       manner specified by section 6 of the GNU GPL for conveying+       Corresponding Source.++       1) Use a suitable shared library mechanism for linking with the+       Library.  A suitable mechanism is one that (a) uses at run time+       a copy of the Library already present on the user's computer+       system, and (b) will operate properly with a modified version+       of the Library that is interface-compatible with the Linked+       Version. ++   e) Provide Installation Information, but only if you would otherwise+   be required to provide such information under section 6 of the+   GNU GPL, and only to the extent that such information is+   necessary to install and execute a modified version of the+   Combined Work produced by recombining or relinking the+   Application with a modified version of the Linked Version. (If+   you use option 4d0, the Installation Information must accompany+   the Minimal Corresponding Source and Corresponding Application+   Code. If you use option 4d1, you must provide the Installation+   Information in the manner specified by section 6 of the GNU GPL+   for conveying Corresponding Source.)++  5. Combined Libraries.++  You may place library facilities that are a work based on the+Library side by side in a single library together with other library+facilities that are not Applications and are not covered by this+License, and convey such a combined library under terms of your+choice, if you do both of the following:++   a) Accompany the combined library with a copy of the same work based+   on the Library, uncombined with any other library facilities,+   conveyed under the terms of this License.++   b) Give prominent notice with the combined library that part of it+   is a work based on the Library, and explaining where to find the+   accompanying uncombined form of the same work.++  6. Revised Versions of the GNU Lesser General Public License.++  The Free Software Foundation may publish revised and/or new versions+of the GNU Lesser General Public License from time to time. Such new+versions will be similar in spirit to the present version, but may+differ in detail to address new problems or concerns.++  Each version is given a distinguishing version number. If the+Library as you received it specifies that a certain numbered version+of the GNU Lesser General Public License "or any later version"+applies to it, you have the option of following the terms and+conditions either of that published version or of any later version+published by the Free Software Foundation. If the Library as you+received it does not specify a version number of the GNU Lesser+General Public License, you may choose any version of the GNU Lesser+General Public License ever published by the Free Software Foundation.++  If the Library as you received it specifies that a proxy can decide+whether future versions of the GNU Lesser General Public License shall+apply, that proxy's public statement of acceptance of any version is+permanent authorization for you to choose that version for the+Library.
+ Setup.hs view
@@ -0,0 +1,2 @@+import Distribution.Simple+main = defaultMain
+ lib/Control/Etage.hs view
@@ -0,0 +1,13 @@+{-|+This data-flow framework consists mainly of 'Neuron's which are data processing units in data-flow network, receiving and sending+'Impulse's over bidirectional 'Nerve's 'attach'ed to each other. 'Neuron's and 'Nerve's are best 'grow'n in 'Incubation' monad, which takes care of+proper 'grow'ing and 'dissolve'-ing of 'Neuron's. It comes with some example 'Neuron's but you should probably define your own.+-}++module Control.Etage (+  module Control.Etage.Incubator,+  module Control.Etage.Externals+) where++import Control.Etage.Externals+import Control.Etage.Incubator
+ lib/Control/Etage/Chan.hs view
@@ -0,0 +1,180 @@+{-# LANGUAGE DeriveDataTypeable #-}++-----------------------------------------------------------------------------+-- |+-- Module      :  Control.Concurrent.Chan+-- Copyright   :  (c) The University of Glasgow 2001+-- License     :  BSD-style (see the file libraries/base/LICENSE)+-- +-- Maintainer  :  libraries@haskell.org+-- Stability   :  experimental+-- Portability :  non-portable (concurrency)+--+-- Unbounded channels.+--+-----------------------------------------------------------------------------++-- Changes: Eq derived on Chan.++module Control.Etage.Chan+  ( +          -- * The 'Chan' type+        Chan,                   -- abstract++          -- * Operations+        newChan,                -- :: IO (Chan a)+        writeChan,              -- :: Chan a -> a -> IO ()+        readChan,               -- :: Chan a -> IO a+        tryReadChan,            -- :: Chan a -> IO (Maybe a)+        dupChan,                -- :: Chan a -> IO (Chan a)+        unGetChan,              -- :: Chan a -> a -> IO ()+        isEmptyChan,            -- :: Chan a -> IO Bool++          -- * Stream interface+        getChanContents,        -- :: Chan a -> IO [a]+        writeList2Chan,         -- :: Chan a -> [a] -> IO ()+   ) where++import Prelude++import System.IO.Unsafe         ( unsafeInterleaveIO )+import Control.Concurrent.MVar+import Data.Typeable++import Control.Exception.Base++-- A channel is represented by two @MVar@s keeping track of the two ends+-- of the channel contents,i.e.,  the read- and write ends. Empty @MVar@s+-- are used to handle consumers trying to read from an empty channel.++-- |'Chan' is an abstract type representing an unbounded FIFO channel.+data Chan a+ = Chan (MVar (Stream a))+        (MVar (Stream a))+ deriving (Eq, Typeable)++type Stream a = MVar (ChItem a)++data ChItem a = ChItem a (Stream a)++-- See the Concurrent Haskell paper for a diagram explaining the+-- how the different channel operations proceed.++-- @newChan@ sets up the read and write end of a channel by initialising+-- these two @MVar@s with an empty @MVar@.++-- |Build and returns a new instance of 'Chan'.+newChan :: IO (Chan a)+newChan = do+   hole  <- newEmptyMVar+   readVar  <- newMVar hole+   writeVar <- newMVar hole+   return (Chan readVar writeVar)++-- To put an element on a channel, a new hole at the write end is created.+-- What was previously the empty @MVar@ at the back of the channel is then+-- filled in with a new stream element holding the entered value and the+-- new hole.++-- |Write a value to a 'Chan'.+writeChan :: Chan a -> a -> IO ()+writeChan (Chan _ writeVar) val = do+  new_hole <- newEmptyMVar+  modifyMVar_ writeVar $ \old_hole -> do+    putMVar old_hole (ChItem val new_hole)+    return new_hole++-- |Read the next value from the 'Chan'.+readChan :: Chan a -> IO a+readChan (Chan readVar _) = do+  modifyMVar readVar $ \read_end -> do+    (ChItem val new_read_end) <- readMVar read_end+        -- Use readMVar here, not takeMVar,+        -- else dupChan doesn't work+    return (new_read_end, val)++{-|+  A semi-non-blocking version of 'readMVar'. The 'tryReadMVar' function returns immediately, with 'Nothing' if the 'MVar' was empty, or+  @'Just' a@ if the 'MVar' was full with contents @a@, after it put the value back (it can block at this stage).+-}+tryReadMVar :: MVar a -> IO (Maybe a)+tryReadMVar m =+  mask_ $ do+    a <- tryTakeMVar m+    case a of+      Nothing -> return Nothing+      Just a' -> do+        putMVar m a'+        return $ Just a'++{-|+  A semi-non-blocking version of 'modifyMVar'. The 'tryModifyMVar' function returns immediately, with 'Nothing' if the 'MVar' was empty, or+  behave as 'modifyMVar' otherwise. This means that it can still block while putting the original (on exception) or new value (otherwise) back.+-}+{-# INLINE tryModifyMVar #-}+tryModifyMVar :: MVar a -> (a -> IO (a, Maybe b)) -> IO (Maybe b)+tryModifyMVar m io =+  mask $ \restore -> do+    a <- tryTakeMVar m+    case a of+      Nothing -> return Nothing+      Just a' -> do+        (a'', b) <- restore (io a') `onException` putMVar m a'+        putMVar m a''+        return b++-- |A non-blocking version of 'readChan'. The 'tryReadChan' function returns immediately, with 'Nothing' if the 'Chan' was empty or would+-- block, or @'Just' a@ with the next value from the 'Chan', otherwise.+tryReadChan :: Chan a -> IO (Maybe a)+tryReadChan (Chan readVar _) = do+  tryModifyMVar readVar $ \read_end -> do+    item <- tryReadMVar read_end+        -- Use tryReadMVar here, not tryTakeMVar,+        -- else dupChan doesn't work+    case item of+      Nothing -> return (read_end, Nothing)+      Just (ChItem val new_read_end) -> return (new_read_end, Just val)++-- |Duplicate a 'Chan': the duplicate channel begins empty, but data written to+-- either channel from then on will be available from both.  Hence this creates+-- a kind of broadcast channel, where data written by anyone is seen by+-- everyone else.+dupChan :: Chan a -> IO (Chan a)+dupChan (Chan _ writeVar) = do+   hole       <- readMVar writeVar+   newReadVar <- newMVar hole+   return (Chan newReadVar writeVar)++-- |Put a data item back onto a channel, where it will be the next item read.+unGetChan :: Chan a -> a -> IO ()+unGetChan (Chan readVar _) val = do+   new_read_end <- newEmptyMVar+   modifyMVar_ readVar $ \read_end -> do+     putMVar new_read_end (ChItem val read_end)+     return new_read_end+{-# DEPRECATED unGetChan "if you need this operation, use Control.Concurrent.STM.TChan instead.  See http://hackage.haskell.org/trac/ghc/ticket/4154 for details" #-}++-- |Returns 'True' if the supplied 'Chan' is empty.+isEmptyChan :: Chan a -> IO Bool+isEmptyChan (Chan readVar writeVar) = do+   withMVar readVar $ \r -> do+     w <- readMVar writeVar+     let eq = r == w+     eq `seq` return eq+{-# DEPRECATED isEmptyChan "if you need this operation, use Control.Concurrent.STM.TChan instead.  See http://hackage.haskell.org/trac/ghc/ticket/4154 for details" #-}++-- Operators for interfacing with functional streams.++-- |Return a lazy list representing the contents of the supplied+-- 'Chan', much like 'System.IO.hGetContents'.+getChanContents :: Chan a -> IO [a]+getChanContents ch+  = unsafeInterleaveIO (do+        x  <- readChan ch+        xs <- getChanContents ch+        return (x:xs)+    )++-- |Write an entire list of items to a 'Chan'.+writeList2Chan :: Chan a -> [a] -> IO ()+writeList2Chan ch ls = sequence_ (map (writeChan ch) ls)
+ lib/Control/Etage/Dump.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, NamedFieldPuns #-}++{-|+This module defines a 'Neuron' which dumps all 'Impulse's it receives. You 'grow' it in 'Incubation' by using something like:++> nerveDump <- growNeuron (\o -> o { showInsteadOfDump = True }) :: NerveOnlyFor DumpNeuron++It is an example of a 'Neuron' which can recieve any 'Impulse' type.+-}++module Control.Etage.Dump (+  DumpNeuron,+  DumpFromImpulse,+  DumpForImpulse,+  DumpOptions,+  NeuronFromImpulse,+  NeuronForImpulse(..),+  NeuronOptions(..)+) where++import Control.Monad+import Data.Typeable+import System.IO++import Control.Etage++data DumpNeuron = DumpNeuron DumpOptions deriving (Typeable)++-- | 'Impulse's from 'DumpNeuron'. This 'Neuron' does not define any 'Impulse's it would send.+type DumpFromImpulse = NeuronFromImpulse DumpNeuron+-- | 'Impulse's for 'DumpNeuron'. This 'Neuron' can recieve any 'Impulse' type.+type DumpForImpulse = NeuronForImpulse DumpNeuron+{-|+Options for 'DumpNeuron'. Those options are defined:++[@handle :: 'Handle'@] 'Handle' to which it dumps. Default is 'stdout'.++[@showInsteadOfDump :: 'Bool'@] Should it use 'show' when dumping 'Impulse's? By default it dumps 'impulseTime' and+'impulseValue' values.+-}+type DumpOptions = NeuronOptions DumpNeuron++-- | Impulse instance for 'DumpNeuron'.+instance Impulse DumpFromImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++-- | Impulse instance for 'DumpNeuron'.+instance Impulse DumpForImpulse where+  impulseTime (DumpForImpulse i) = impulseTime i+  impulseValue (DumpForImpulse i) = impulseValue i++deriving instance Show DumpFromImpulse++instance Show DumpForImpulse where+  show (DumpForImpulse i) = show i++instance Eq DumpForImpulse where+  (==) = impulseEq++instance Ord DumpForImpulse where+  compare = impulseCompare++-- | A 'Neuron' which dumps all 'Impulse's it receives.+instance Neuron DumpNeuron where+  data NeuronFromImpulse DumpNeuron+  data NeuronForImpulse DumpNeuron where+    DumpForImpulse :: Impulse i => i -> DumpForImpulse+  data NeuronOptions DumpNeuron = DumpOptions {+      handle :: Handle,+      showInsteadOfDump :: Bool+    } deriving (Eq, Show)+  +  mkDefaultOptions = return DumpOptions {+      handle = stdout,+      showInsteadOfDump = False+    }+  +  grow options = return $ DumpNeuron options+  +  live nerve (DumpNeuron DumpOptions { handle, showInsteadOfDump }) = forever $ do+    i <- getForNeuron nerve -- we want all not just newest+    if showInsteadOfDump+      then hPutStrLn handle $ show i+      else hPutStrLn handle $ show (impulseTime i) ++ ": " ++ show (impulseValue i)++instance Impulse i => ImpulseTranslator i DumpForImpulse where+  translate i = [DumpForImpulse i]
+ lib/Control/Etage/Externals.hs view
@@ -0,0 +1,430 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, FlexibleContexts, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, NamedFieldPuns #-}++module Control.Etage.Externals (+  -- * 'Neuron's and 'Impulse's+  -- | Using only built-in 'Neuron's is not much fun. Main idea of this data-flow framework is to ease development of your own+  -- 'Neuron's (data processing units).+  Neuron(..),+  attach',+  detach,+  detachAndWait,+  detachMany,+  detachManyAndWait,+  LiveNeuron,+  DissolveException,+  dissolving,+  DissolvingException,++  mkNeuronMapOnRandomCapability,+  NeuronMapCapability(..),++  defaultOptions,+  +  Impulse(..),+  ImpulseTime,+  ImpulseValue,+  ImpulseTranslator(..),++  translateAndSend,++  Nerve,+  AxonConductive,+  AxonNonConductive,++  -- * Sending and receiving outside the 'Neuron'+  -- | Those functions are used outside the 'Neuron' when interacting with it.+  sendForNeuron,+  getFromNeuron,+  maybeGetFromNeuron,+  slurpFromNeuron,+  waitAndSlurpFromNeuron,+  getContentsFromNeuron,+  sendListForNeuron,++  -- * Sending and receiving inside the 'Neuron'+  -- | Those functions are used inside the 'Neuron' when implementing it.+  sendFromNeuron,+  getForNeuron,+  maybeGetForNeuron,+  slurpForNeuron,+  waitAndSlurpForNeuron,+  getNewestForNeuron,+  getContentsForNeuron,+  sendListFromNeuron,++  -- * Helper functions+  prepareEnvironment,+  getCurrentImpulseTime,+  impulseEq,+  impulseCompare+) where++import Prelude hiding (catch)++import Control.Concurrent hiding (Chan, writeChan, readChan, isEmptyChan, getChanContents)+import Data.Data+import Data.Function+import Data.List+import Control.Exception+import Data.Time.Clock.POSIX+import GHC.Conc (forkOnIO, numCapabilities)+import System.IO+import System.Posix.Signals+import System.Random++import Control.Etage.Chan+import Control.Etage.Internals++{-|+Sends an 'Impulse' from a 'Neuron'. 'Nerve' does not need to be conductive, 'Impulse' will be silently dropped in this case.+-}+sendFromNeuron :: Nerve from fromConductivity for forConductivity -> from -> IO ()+sendFromNeuron (Nerve (Axon chan) _) i = writeChan chan i+sendFromNeuron (Nerve NoAxon _) _ = return () -- we allow sending but ignore so that same Neuron defintion can be used on all kinds of Nerves++{-|+Gets an 'Impulse' from a 'Neuron'. It blocks until an 'Impulse' is available. 'Nerve' has to be conductive.+-}+getFromNeuron :: Nerve from AxonConductive for forConductivity -> IO from+getFromNeuron (Nerve (Axon chan) _) = readChan chan++{-|+Similar to 'getFromNeuron' just that it does not block if 'Impulse' is not available.+-}+maybeGetFromNeuron :: Nerve from AxonConductive for forConductivity -> IO (Maybe from)+maybeGetFromNeuron (Nerve (Axon chan) _) = tryReadChan chan++{-|+Gets all immediately available 'Impulse's from a 'Neuron'. There could be no 'Impulse's available and thus the result is an empty+list. Oldest 'Impulse' is the last in the list. 'Nerve' has to be conductive.+-}+slurpFromNeuron :: Nerve from AxonConductive for forConductivity -> IO [from]+slurpFromNeuron (Nerve (Axon chan) _) = slurpChan chan++{-|+Similar to 'slurpFromNeuron' but it waits for at least one 'Impulse'.+-}+waitAndSlurpFromNeuron :: Nerve from AxonConductive for forConductivity -> IO [from]+waitAndSlurpFromNeuron nerve = do+  oldest <- getFromNeuron nerve+  others <- slurpFromNeuron nerve+  return $ others ++ [oldest]++{-|+Returns a lazy list of 'Impulse's from a 'Neuron'. 'Nerve' has to be conductive.+-}+getContentsFromNeuron :: Nerve from AxonConductive for forConductivity -> IO [from]+getContentsFromNeuron (Nerve (Axon chan) _) = getChanContents chan++{-|+Sends all 'Impulses' from a given list to a 'Neuron'. 'Nerve' does not need to be conductive, 'Impulse's will be silently+dropped in this case.+-}+sendListFromNeuron :: Nerve from fromConductivity for forConductivity -> [from] -> IO ()+sendListFromNeuron nerve = mapM_ (sendFromNeuron nerve)++{-|+Sends an 'Impulse' to a 'Neuron'. 'Nerve' has to be conductive.+-}+sendForNeuron :: Nerve from fromConductivity for AxonConductive -> for -> IO ()+sendForNeuron (Nerve _ (Axon chan)) = writeChan chan++{-|+Gets an 'Impulse' for a 'Neuron'. It blocks until an 'Impulse' is available. 'Nerve' does not need to be conductive,+it will block indefinitely (until an exception) in this case.+-}+getForNeuron :: Nerve from fromConductivity for forConductivity -> IO for+getForNeuron (Nerve _ (Axon chan)) = readChan chan+getForNeuron (Nerve _ NoAxon) = waitForException++{-|+Similar to 'getForNeuron' just that it does not block if 'Impulse' is not available. 'Nerve' does not need to be conductive,+it will always return 'Nothing' in this case.+-}+maybeGetForNeuron :: Nerve from fromConductivity for forConductivity -> IO (Maybe for)+maybeGetForNeuron (Nerve _ (Axon chan)) = tryReadChan chan+maybeGetForNeuron (Nerve _ NoAxon) = return Nothing -- we allow getting but return Nothing so that same Neuron defintion can be used on all kinds of Nerves++{-|+Gets all immediately available 'Impulse's for a 'Neuron'. There could be no 'Impulse's available and thus the result is an empty+list. Oldest 'Impulse' is the last in the list. 'Nerve' does not need to be conductive, it will always return an empty list+in this case.+-}+slurpForNeuron :: Nerve from fromConductivity for forConductivity -> IO [for]+slurpForNeuron (Nerve _ (Axon chan)) = slurpChan chan+slurpForNeuron (Nerve _ NoAxon) = return [] -- we allow getting but return [] so that same Neuron defintion can be used on all kinds of Nerves++{-|+Similar to 'slurpForNeuron' but it waits for at least one 'Impulse'. 'Nerve' does not need to be conductive,+it will block indefinitely (until an exception) in this case.+-}+waitAndSlurpForNeuron :: Nerve from fromConductivity for forConductivity -> IO [for]+waitAndSlurpForNeuron nerve = do+  oldest <- getForNeuron nerve+  others <- slurpForNeuron nerve+  return $ others ++ [oldest]++{-|+Similar to 'waitAndSlurpForNeuron' but it will return only the newest 'Impulse' for every 'NeuronForImpulse' data type constructor.+This is the same as @head \<$\> waitAndSlurpForNeuron@ iff 'NeuronForImpulse' has only one constructor defined. Otherwise it can+return multiple 'Impulse's, for each constructor one.+-}+getNewestForNeuron :: Data for => Nerve from fromConductivity for forConductivity -> IO [for]+getNewestForNeuron nerve = do+  impulses <- waitAndSlurpForNeuron nerve+  return $ nubBy ((==) `on` toConstr) impulses++{-|+Returns a lazy list of 'Impulse's for a 'Neuron'. 'Nerve' does not need to be conductive,+it will block indefinitely (until an exception) in this case.+-}+getContentsForNeuron :: Nerve from fromConductivity for forConductivity -> IO [for]+getContentsForNeuron (Nerve _ (Axon chan)) = getChanContents chan+getContentsForNeuron (Nerve _ NoAxon) = waitForException++{-|+Sends all 'Impulses' from a given list to a 'Neuron'. 'Nerve' has to be conductive.+-}+sendListForNeuron :: Nerve from fromConductivity for AxonConductive -> [for] -> IO ()+sendListForNeuron nerve = mapM_ (sendForNeuron nerve)++-- First-in (oldest) element in the channel is last in the list+slurpChan :: Chan a -> IO [a]+slurpChan chan = slurpChan' []+  where slurpChan' cs = do+          mc <- tryReadChan chan+          case mc of+            Nothing -> return cs+            Just c  -> slurpChan' (c:cs)++{-|+'Neuron's can be mapped to capabilities (OS threads) in different ways. The best is to let Haskell decide the best capability+(and also move 'Neuron's among them as necessary) by using 'NeuronFreelyMapOnCapability' value, but sometimes because of an external+(FFI) library limitations you have to map 'Neuron' to a fixed capability, you can use 'NeuronMapOnCapability' for that.++Sometimes it is not important to which capability you map a 'Neuron', just that few 'Neuron's are mapped to the same. You can+use 'mkNeuronMapOnRandomCapability' to create such 'NeuronMapCapability' value.+-}+data NeuronMapCapability =+    NeuronMapOnCapability Int -- ^ Map a 'Neuron' to fixed capability.+  | NeuronFreelyMapOnCapability -- ^ Let Haskell decide on which capability is best to map a 'Neuron' at a given time.+  deriving (Eq, Ord, Read, Show)++{-|+Creates a 'NeuronMapOnCapability' value with a chosen capability picked by random. Useful when you have to map few 'Neuron's to the+same capability (because of an eternal (FFI) library limitations) but it does not matter to which one. So you create this value+and pass it as an option to all those 'Neuron's, making sure that they will return it with their 'getNeuronMapCapability' method.+For example, sometimes you have to assure that both your 'Neuron' and "Control.Etage.Worker" 'Neuron' are running on the same+capability so that you can correctly offload lengthly IO actions to it. This makes both 'Neuron's in fact still running in one+thread (which is often a limitation of external libraries), Haskell taking care of interleaving 'Neuron's IO actions.+-}+mkNeuronMapOnRandomCapability :: IO NeuronMapCapability+mkNeuronMapOnRandomCapability = do+  c <- randomRIO (1, numCapabilities)+  return $ NeuronMapOnCapability c++divideNeuron :: Neuron n => NeuronOptions n -> IO () -> IO NeuronId+divideNeuron options a = fork a+  where fork = case getNeuronMapCapability options of+                 NeuronFreelyMapOnCapability -> forkIO+                 NeuronMapOnCapability c     -> forkOnIO c++deriving instance Typeable1 NeuronFromImpulse+deriving instance Typeable1 NeuronForImpulse+deriving instance Typeable1 NeuronOptions++-- | A type class which defines common methods and data types of 'Neuron's.+class (Typeable n, Impulse (NeuronFromImpulse n), Impulse (NeuronForImpulse n)) => Neuron n where+  -- | A data type for 'Impulses' send from a 'Neuron'. 'Neuron' does not really need to use them.+  data NeuronFromImpulse n+  -- | A data type for 'Impulses' send for a 'Neuron'. 'Neuron' does not really need to use them.+  data NeuronForImpulse n+  -- | A data type for options. 'Neuron' does not really need to use them.+  data NeuronOptions n+  +  -- | Method which returns default values for options. By default returns 'undefined'.+  mkDefaultOptions :: IO (NeuronOptions n)+  +  -- | Method which returns how should 'Neuron' be mapped on capabilities (OS threads). By default returns+  -- 'NeuronFreelyMapOnCapability'.+  getNeuronMapCapability :: NeuronOptions n -> NeuronMapCapability+  +  -- | The first phase in a life-cycle of a 'Neuron' is to 'grow'. In this phase everything should be prepared and initialized.+  -- It returns a 'Neuron' value which is then passed to next phases. If you want to use 'NeuronOptions' also in those phases+  -- you should store them in the 'Neuron' value. By default returns 'undefined'.+  grow :: NeuronOptions n -> IO n+  -- | After 'grow'ing 'Neuron' 'live's. This is a phase in which it should read 'Impulse's from its 'Nerve' and send them back,+  -- as defined by its logic/purpose. Some 'Neuron's only read, some only send, some do both or none.+  --+  -- Most 'Neuron's do never finish this phase on its own (only by exception), but if your 'Neuron' does, consider using 'dissolving'+  -- at the end which initiates dissolving also elsewhere in the network (or in the parent 'Neuron', if it has one). Examples+  -- of such 'Neuron's are "Control.Etage.Timeout" and "Control.Etage.Sequence" (once a given sequence ends).+  --+  -- By default it blocks indefinitely (until an exception).+  live :: Nerve (NeuronFromImpulse n) fromConductivity (NeuronForImpulse n) forConductivity -> n -> IO ()+  -- | In this phase everything should be cleaned up and deinitialized. If you have 'grow'n child 'Neuron's you should take care+  -- here to 'dissolve' them too. You can use 'detachAndWait' for that (or 'detachManyAndWait' if you have more of them).+  -- By default it does nothing.+  dissolve :: n -> IO ()++  -- | This method should take care of 'grow'ing a 'Neuron' with a given 'Nerve' 'attach'ed to it. It takes a function which+  -- changes default options and returns a 'LiveNeuron' value which can be used for 'detach'ing (and thus 'dissolve'-ing) the 'Neuron'.+  -- It should create a thread for a 'Neuron' to 'live' in and it should assure proper cleanup and 'dissolve'-ing.+  --+  -- By default it calls 'attach'' to do all that.+  attach :: (NeuronOptions n -> NeuronOptions n) -> Nerve (NeuronFromImpulse n) fromConductivity (NeuronForImpulse n) forConductivity -> IO LiveNeuron++  mkDefaultOptions = return undefined++  getNeuronMapCapability _ = NeuronFreelyMapOnCapability++  grow _ = return undefined+  dissolve _ = return ()+  live _ _ = waitForException+  +  attach = attach'++{-|+Default implementation for 'attach' method. It takes a function which changes default options and returns a 'LiveNeuron' value+which can be used for 'detach'ing (and thus 'dissolve'-ing) the 'Neuron'.++It changes default options according to a given function, creates thread for a 'Neuron' to live in based on 'getNeuronMapCapability',+'grow's a 'Neuron', runs 'live' and prepares everything for cleanup with 'dissolve', whether because 'live' finished or because of an+exception. In the later case it rethrows an exception in the parent 'Neuron' (or in 'Incubation'). It also signals the 'Neuron'+has 'dissolve'd for 'detachAndWait' and 'detachManyAndWait'.+-}+attach' :: Neuron n => (NeuronOptions n -> NeuronOptions n) -> Nerve (NeuronFromImpulse n) fromConductivity (NeuronForImpulse n) forConductivity -> IO LiveNeuron+attach' optionsSetter nerve = mask $ \restore -> do+  currentThread <- myThreadId+  dissolved <- newEmptySampleVar+  defOptions <- mkDefaultOptions+  let options = optionsSetter defOptions+  nid <- divideNeuron options $+           bracket (grow options) dissolve (restore . live nerve) `catches` [+               Handler (\(_ :: DissolveException) -> return ()), -- we ignore DissolveException+               Handler (\(e :: SomeException) -> uninterruptible $ throwTo currentThread e)+             ] `finally` uninterruptible (writeSampleVar dissolved ())+  return $ LiveNeuron dissolved nid++{-|+An exception which initiates 'dissolve'-ing of a 'Neuron'. Should be thrown inside the 'Neuron' with passing its 'Neuron' value as+argument (as passed to 'live' method). For throwing outside the 'Neuron' use 'DissolveException' (or simply 'detach' and others).+-}+data DissolvingException = DissolvingException String deriving (Show, Typeable)++instance Exception DissolvingException++{-|+Initiates 'dissolve'-ing of a 'Neuron' by throwing a 'DissolvingException'. To be used inside a 'Neuron' to maybe prematurely+finish its life but more importantly to initiate 'dissolve'-ing in the parent 'Neuron' (or in 'Incubation'). As an argument+it is accustomed to pass a 'Neuron' value as passed to 'live' method.+-}+dissolving :: Show n => n -> IO a+dissolving n = throwIO $ DissolvingException (show n)++{-|+An exception which initiates 'dissolve'-ing of a 'Neuron'. Should be thrown outside the 'Neuron' to the 'Neuron'. For+throwing inside the 'Neuron' use 'DissolvingException' (or simply 'dissolving').+-}+data DissolveException = DissolveException deriving (Show, Typeable)++instance Exception DissolveException++{-|+Initiates 'dissolve'-ing of a 'Neuron' by throwing a 'DissolveException'. To be used outside of a 'Neuron'.+-}+detach :: LiveNeuron -> IO ()+detach (LiveNeuron _ neuronId) = mask_ . uninterruptible $ throwTo neuronId DissolveException++{-|+Similar to 'detachAndWait' but it also waits 'Neuron' to finish 'dissolve'-ing.+-}+detachAndWait :: LiveNeuron -> IO ()+detachAndWait n = detachManyAndWait [n]++{-|+Similar to 'detach' but for many 'Neuron's at the same time. It initiates 'dissolve'-ing in the list order.+-}+detachMany :: [LiveNeuron] -> IO ()+detachMany = mask_ . mapM_ detach++{-|+Similar to 'detachAndWait' but for many 'Neuron's at the same time. It first initiates 'dissolve'-ing in the list order and then+wait for all 'Neuron's to finish 'dissolve'-ing.+-}+detachManyAndWait :: [LiveNeuron] -> IO ()+detachManyAndWait neurons = mask_ $ do+  detachMany neurons+  mapM_ (\(LiveNeuron d _) -> uninterruptible $ readSampleVar d) neurons++-- Some IO operations are interruptible, better than to make them uninterruptible (which can cause deadlocks) we simply retry interrupted operation+-- For this to really work all interruptible operations should be wrapped like this (so it is not good idea to use IO operations in such code sections)+uninterruptible :: IO a -> IO a+uninterruptible a = mask_ $ a `catch` (\(_ :: SomeException) -> uninterruptible a)++-- | This type class defines a method for translating between 'Impulse' types.+class (Impulse i, Impulse j) => ImpulseTranslator i j where+  -- | 'translate' gets an 'Impulse' of one type and returns a list of 'Impulses' of another type.+  --+  -- 'Impulse's should be translated meaningfully, translating values as possible and filling others with reasonable defaults.+  -- Timestamp should be just copied (translation should be seen as an instantaneous operation as it is a byproduct of type+  -- constraints and chosen description format of 'Impulse's and not something found otherwise in a network.+  -- Time spend in translation should be seen as a part of time spend in sending of an 'Impulse' along a 'Nerve'.+  --+  -- One 'Impulse' can be translated into multiple other 'Impulse's as sometimes some 'Impulse's are higher level than other.+  -- (Translating multiple 'Impulse's into one 'Impulse' requires keeping a state and should be done in a 'Neuron'.) The order is+  -- important as first 'Impulse's in the list are send first along a 'Nerve'.+  translate :: i -> [j]++{-|+Function which can be used as an argument to 'growNeuron' or 'attach' which leaves default options as they are.++In fact it is just an 'id'entity function.+-}+defaultOptions :: Neuron n => NeuronOptions n -> NeuronOptions n+defaultOptions = id++{-|+Helper function which does some common initialization. Currently it sets 'stderr' buffering to 'LineBuffering' so that when+multiple 'Neuron's print to 'stderr' output is not mixed. It also installs handlers for 'keyboardSignal' and 'softwareTermination'+signals so that cleanup in 'Incubation' works as expected.+-}+prepareEnvironment :: IO ()+prepareEnvironment = do+  hSetBuffering stderr LineBuffering+  +  mainThreadId <- myThreadId+  +  -- TODO: User interrupt sometimes hangs dissolving (does it still in GHC 7.0?)+  _ <- installHandler keyboardSignal (Catch (throwTo mainThreadId UserInterrupt)) Nothing -- sigINT+  _ <- installHandler softwareTermination (Catch (throwTo mainThreadId UserInterrupt)) Nothing -- sigTERM+  +  return ()++{-|+Translates (if necessary 'ImpulseTranslator' exists) an 'Impulse' and sends translation to 'Neuron'.+-}+translateAndSend :: ImpulseTranslator i for => Nerve from fromConductivity for AxonConductive -> i -> IO ()+translateAndSend nerve i = mapM_ (sendForNeuron nerve) $ translate i++{-|+Returns current time. Useful when creating new 'Impulse's.+-}+getCurrentImpulseTime :: IO ImpulseTime+getCurrentImpulseTime = getPOSIXTime++{-|+This function defines equality between 'Impulse's as equality of 'impulseTime' and 'impulseValue' values. Useful for 'Neuron's which+operate on all types of 'Impulse's and want 'Eq' defined on their 'Impulse's. Examples of such 'Neuron's are "Control.Etage.Dump"+and "Control.Etage.Function".+-}+impulseEq :: (Impulse i, Impulse j) => i -> j -> Bool+impulseEq a b = impulseTime a == impulseTime b && impulseValue a == impulseValue b++{-|+This function defines ordering between 'Impulse's as ordering first by 'impulseTime' values and then by 'impulseValue' values.+Useful for 'Neuron's which operate on all types of 'Impulse's and want 'Ord' defined on their 'Impulse's. Examples of such+'Neuron's are "Control.Etage.Dump" and "Control.Etage.Function".+-}+impulseCompare :: (Impulse i, Impulse j) => i -> j -> Ordering+impulseCompare a b = (impulseTime a, impulseValue a) `compare` (impulseTime b, impulseValue b)
+ lib/Control/Etage/Fail.hs view
@@ -0,0 +1,57 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, DeriveDataTypeable, TypeSynonymInstances, StandaloneDeriving, NamedFieldPuns #-}++{-|+This module defines a simple 'Neuron' which just fails (throws a 'DissolvingException') in 'grow'ing phase. It can be used to test+error recovery and cleanup in 'grow'ing phase or early stages of 'live'ing phase in other 'Neuron's by using something like:++> _ <- growNeuron defaultOptions :: NerveNone FailNeuron++somewhere among (or after) 'growNeuron' calls for other 'Neuron's in 'Incubation'.+-}++module Control.Etage.Fail (+  FailNeuron,+  FailFromImpulse,+  FailForImpulse,+  FailOptions,+  NeuronFromImpulse,+  NeuronForImpulse,+  NeuronOptions+) where++import Data.Typeable++import Control.Etage++data FailNeuron deriving (Typeable)++instance Show FailNeuron where+  show = show . typeOf++-- | 'Impulse's from 'FailNeuron'. This 'Neuron' does not define any 'Impulse's it would send.+type FailFromImpulse = NeuronFromImpulse FailNeuron+-- | 'Impulse's for 'FailNeuron'. This 'Neuron' does not define any 'Impulse's it would receive.+type FailForImpulse = NeuronForImpulse FailNeuron+-- | Options for 'FailNeuron'. This 'Neuron' does not define any options.+type FailOptions = NeuronOptions FailNeuron++-- | Impulse instance for 'FailNeuron'.+instance Impulse FailFromImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++-- | Impulse instance for 'FailNeuron'.+instance Impulse FailForImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++deriving instance Show FailFromImpulse+deriving instance Show FailForImpulse++-- | A simple 'Neuron' which just fails in 'grow'ing phase.+instance Neuron FailNeuron where+  data NeuronFromImpulse FailNeuron+  data NeuronForImpulse FailNeuron+  data NeuronOptions FailNeuron+  +  grow _ = dissolving (undefined :: FailNeuron)
+ lib/Control/Etage/Function.hs view
@@ -0,0 +1,100 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, DeriveDataTypeable, TypeSynonymInstances, NamedFieldPuns, BangPatterns #-}++{-|+This module defines a 'Neuron' which sends results of applying a given function to recieved 'Impulse's. You 'grow' it in+'Incubation' by using something like:++> nerveFunction <- growNeuron (\o -> o { function = negate . sum }) :: NerveBoth FunctionNeuron++It is an example of a 'Neuron' which can operate on any 'Impulse' type by using 'impulseValue' type class method.+-}++module Control.Etage.Function (+  FunctionNeuron,+  FunctionFromImpulse,+  FunctionForImpulse,+  FunctionOptions,+  NeuronFromImpulse,+  NeuronForImpulse,+  NeuronOptions(..)+) where++import Control.Applicative+import Control.Monad+import Data.Time.Clock+import Data.Typeable++import Control.Etage++defaultFunction :: [Rational] -> Rational+defaultFunction = sum++data FunctionNeuron = FunctionNeuron FunctionOptions deriving (Typeable)++instance Show FunctionNeuron where+  show = show . typeOf++{-|+'Impulse's from 'FunctionNeuron'. This 'Impulse' constructor is defined:++[@Value { impulseTimestamp :: 'ImpulseTime', value :: 'Rational', evaluationTime :: 'NominalDiffTime' }@]+@impulseTimestamp@ is time when the result was evaluated, @value@ contains the evaluated result, @evaluationTime@ is how long the+evaluation took.+-}+type FunctionFromImpulse = NeuronFromImpulse FunctionNeuron+-- | 'Impulse's for 'FunctionNeuron'. This 'Neuron' can recieve any 'Impulse' type.+type FunctionForImpulse = NeuronForImpulse FunctionNeuron+{-|+Options for 'FunctionNeuron'. This option is defined:++[@function :: \['Rational'\] -> 'Rational'@] The function to apply to recieved 'Impulse's. Default is 'sum'.+-}+type FunctionOptions = NeuronOptions FunctionNeuron++-- | Impulse instance for 'FunctionNeuron'.+instance Impulse FunctionFromImpulse where+  impulseTime Value { impulseTimestamp } = impulseTimestamp+  impulseValue Value { value } = [value]++-- | Impulse instance for 'FunctionNeuron'.+instance Impulse FunctionForImpulse where+  impulseTime (FunctionForImpulse i) = impulseTime i+  impulseValue (FunctionForImpulse i) = impulseValue i++instance Show FunctionForImpulse where+  show (FunctionForImpulse i) = show i++instance Eq FunctionForImpulse where+  (==) = impulseEq++instance Ord FunctionForImpulse where+  compare = impulseCompare++-- | A 'Neuron' which sends results of a given function for recieved 'Impulse's.+instance Neuron FunctionNeuron where+  data NeuronFromImpulse FunctionNeuron = Value {+      impulseTimestamp :: ImpulseTime, -- time is first so that ordering is first by time+      value :: Rational,+      evaluationTime :: NominalDiffTime+    } deriving (Eq, Ord, Read, Show)+  data NeuronForImpulse FunctionNeuron where+    FunctionForImpulse :: Impulse i => i -> FunctionForImpulse+  data NeuronOptions FunctionNeuron = FunctionOptions {+      function :: [Rational] -> Rational+    }+  +  mkDefaultOptions = return FunctionOptions {+      function = defaultFunction+    }+  +  grow options = return $ FunctionNeuron options+  +  live nerve (FunctionNeuron FunctionOptions { function }) = forever $ do+    i <- head <$> waitAndSlurpForNeuron nerve -- just newest+    time1 <- getCurrentImpulseTime+    let !r = function . impulseValue $ i+    time2 <- getCurrentImpulseTime+    sendFromNeuron nerve Value { impulseTimestamp = time2, value = r, evaluationTime = time2 - time1 }++instance Impulse i => ImpulseTranslator i FunctionForImpulse where+  translate i = [FunctionForImpulse i]
+ lib/Control/Etage/Incubator.hs view
@@ -0,0 +1,246 @@+{-# LANGUAGE GADTs, FlexibleInstances, FlexibleContexts, GeneralizedNewtypeDeriving, ScopedTypeVariables #-}++module Control.Etage.Incubator (+  -- * Incubation+  -- | 'Incubation' is a 'Monad' helping 'grow'ing a network of 'Neuron's and 'Nerve's while taking care of all the details and+  -- cleanup. It is the recommended and preferred way for 'grow'ing your networks.+  --+  -- A basic example of using 'Incubation' and of this data-flow framework would be a program where one 'Neuron' is generating+  -- 'Impulse's with random values ("Control.Etage.Sequence") and another 'Neuron' printing them out ("Control.Etage.Dump"):+  --+  -- > main = do+  -- >   prepareEnvironment+  -- >   +  -- >   incubate $ do+  -- >     nerveRandom <- growNeuron defaultOptions :: NerveOnlyFrom (SequenceNeuron Int)+  -- >     nerveDump <- growNeuron defaultOptions :: NerveOnlyFor DumpNeuron+  -- >     +  -- >     nerveRandom `attachTo` [Translatable nerveDump]+  incubate,+  growNeuron,+  attachTo,+  NerveBoth,+  NerveNone,+  NerveOnlyFrom,+  NerveOnlyFor,+  Incubation,+  Translatable(..),+  -- * Internals+  -- | Be careful when using those functions as you have to assure your network is well-behaved:+  --+  -- * You should assure that for all 'Nerve's you defined as conductive from 'Neuron's and 'attach'ed them to 'Neuron's you+  -- really receive sent impulses, otherwise there will be a memory leak. You should probably just define those nerves+  -- as 'NerveOnlyFor' or 'NerveNone'.+  --+  -- * If you 'attach' multiple 'Neuron's to the same 'Nerve' you should probably take care of branching 'Nerve's correctly. For+  -- example, if multiple 'Neuron's are receiving from the same 'Nerve' you should first branch 'Nerve' with 'branchNerveFor',+  -- otherwise 'Neuron's will not receive all 'Impulse's as some other 'Neuron' will receive it first (but this can be also+  -- intentional).+  -- On the other hand, if you are receiving from the same 'Neuron' at multiple parts of the network you should branch+  -- 'Nerve' with 'branchNerveFrom' for each such part (or not, if intentional). This also holds for 'propagate': if you are using+  -- it multiple times with the same 'Nerve' as @from@ argument you should first branch it with 'branchNerveFrom'. (But it is+  -- probably easier to just use it once and list all @for@ 'Nerve's together.)+  --+  -- * And of course in a case of an exception or in general when your are doing cleanup you should assure that 'detach'+  -- (or 'detachAndWait') is called for each 'LiveNeuron' (or 'detachMany' or 'detachManyAndWait').+  --+  -- They are exposed so that you can decouple 'grow'ing and 'dissolve'-ing handling and that you can attach 'Nerve's+  -- in some special ways. If you do not need that use 'Incubation'.+  --+  -- For example, your 'Neuron' can 'grow' and use another 'Neuron' (in this example "Control.Etage.Worker") like this:+  --+  -- > data YourNeuron = YourNeuron ... LiveNeuron (Nerve WorkerFromImpulse AxonNonConductive WorkerForImpulse AxonConductive) deriving (Typeable)+  -- >+  -- > grow options = do+  -- >   ...+  -- >   nerve <- growNerve+  -- >   bracketOnError (attach defaultOptions nerve) detachAndWait $ \neuron -> do+  -- >     ...+  -- >     return $ YourNeuron ... neuron nerve+  -- >+  -- > dissolve (YourNeuron ... neuron _) = do+  -- >   detachAndWait neuron+  -- >   ...+  --+  -- We use 'bracketOnError' there to be sure that 'Neuron' is properly 'dissolve'd even if there is an exception later on in+  -- 'grow'ing the parent 'Neuron'. And we use 'detachAndWait' so that we give time for child 'Neuron' to 'dissolve' properly.+  -- Which 'Neuron' you want is in this case inferred from the type of the 'Nerve' you defined.+  growNerve,+  propagate,+  branchNerveFor,+  branchNerveFrom,+  branchNerveBoth+) where++import Control.Applicative+import Control.Exception+import Control.Monad+import Control.Monad.Operational+import Control.Monad.Trans+import Data.List+import Data.Typeable+import System.IO++import Control.Etage.Chan+import Control.Etage.Propagate+import Control.Etage.Internals+import Control.Etage.Externals++data IncubationOperation a where+  NeuronOperation :: (Neuron n, GrowAxon (Axon (NeuronFromImpulse n) fromConductivity), GrowAxon (Axon (NeuronForImpulse n) forConductivity)) => (NeuronOptions n -> NeuronOptions n) -> IncubationOperation (Nerve (NeuronFromImpulse n) fromConductivity (NeuronForImpulse n) forConductivity)+  AttachOperation :: forall from for forConductivity. (Typeable from, Typeable for, Typeable forConductivity) => Nerve from AxonConductive for forConductivity -> [Translatable from] -> IncubationOperation ()++type Incubation' a = ProgramT IncubationOperation IO a+{-|+An 'Incubation' monad type. It makes sure network is 'grow'n properly and that everything is cleaned up as necessary.+-}+newtype Incubation a = Incubation (Incubation' a) deriving (Monad, MonadIO, Applicative, Functor)++-- TODO: Check if all chans have been attached with type checking (type nats)? (If this checking even shows as useful. And correct.)+{-|+Runs an 'Incubation', 'grow'ing 'Neuron's and 'attach'ing 'Nerve's and after that waiting for them to finish and cleanup.+It rethrows any exception which might have been thrown.+-}+incubate :: Incubation () -> IO ()+incubate (Incubation program) = mask $ \restore -> do+  (neurons, chans, attached) <- restore $ interpret [] [] [] program+  flip finally (detachManyAndWait neurons) $ do+    let na = nub chans \\ nub attached+        typ = unlines . map (\(ChanBox c) -> ' ' : show (neuronTypeOf c)) $ na+    unless (null na) $ hPutStrLn stderr $ "Warning: It seems not all created nerves were attached. This causes a memory leak as send impulses are not received. You should probably just define those nerves as NerveOnlyFor or NerveNone. Dangling nerves for neurons:\n" ++ typ+    restore waitForException++interpret :: [LiveNeuron] -> [ChanBox] -> [ChanBox] -> Incubation' () -> IO ([LiveNeuron], [ChanBox], [ChanBox])+interpret neurons chans attached = viewT >=> eval neurons chans attached+    where eval :: [LiveNeuron] -> [ChanBox] -> [ChanBox] -> ProgramViewT IncubationOperation IO () -> IO ([LiveNeuron], [ChanBox], [ChanBox])+          eval ns cs ats (Return _) = return (ns, cs, ats)+          eval ns cs ats (NeuronOperation optionsSetter :>>= is) = do+            nerve <- liftIO growNerve+            let c = getFromChan nerve+            bracketOnError (attach optionsSetter nerve) detachAndWait $ \n -> interpret (n:ns) (c ++ cs) ats . is $ nerve+          eval ns cs ats (AttachOperation from for :>>= is) = do+            let c = head . getFromChan $ from -- we know there exists from chan as type checking assures that (from is conductive)+            (from', ats') <- if c `notElem` ats+                               then return (from, c:ats)+                               else do+                                 branchFrom <- branchNerveFrom from -- we have to branch from chan as it is attached multiple times+                                 return (branchFrom, ats) -- we store only original nerves in attached list+            propagate from' for+            interpret ns cs ats' . is $ ()++{-|+Grows a 'Neuron', taking a function which changes default options and returning a 'Nerve' 'attach'ed to the 'Neuron'.++Internally it combines 'growNerve' and 'attach'.+-}+growNeuron :: (Neuron n, GrowAxon (Axon (NeuronFromImpulse n) fromConductivity), GrowAxon (Axon (NeuronForImpulse n) forConductivity)) => (NeuronOptions n -> NeuronOptions n) -> Incubation (Nerve (NeuronFromImpulse n) fromConductivity (NeuronForImpulse n) forConductivity)+growNeuron os = Incubation $ singleton (NeuronOperation os)++{-|+Attaches a 'Nerve' to other 'Nerve's so that 'Impulse's send from the 'Neuron' over the first 'Nerve' are received by 'Neuron's+of other 'Nerve's. 'Impulse's are 'propagate'd only in this direction, not in the other. If you want also the other direction use+'attachTo' again for that direction.++Internally it uses 'propagate'.+-}+attachTo :: forall from for forConductivity. (Typeable from, Typeable for, Typeable forConductivity) => Nerve from AxonConductive for forConductivity -> [Translatable from] -> Incubation ()+attachTo n ts = Incubation $ singleton (AttachOperation n ts)++class GrowAxon a where+  growAxon :: IO a++instance Impulse i => GrowAxon (Axon i AxonConductive) where+  growAxon = Axon <$> newChan++instance GrowAxon (Axon i AxonNonConductive) where+  growAxon = return NoAxon++{-|+Grows an unattached 'Nerve'. By specifying type of the 'Nerve' you can specify conductivity of both directions (which is then+type checked for consistency around the program) and thus specify which 'Impulse's you are interested in (and thus limit possible+memory leak). With type of 'Impulse's this 'Nerve' is capable of conducting you can also specify which 'Neuron' you are interested+in 'grow'ing on the one end of the 'Nerve'.++For example, you could grow a 'Nerve' for "Control.Etage.Sequence" 'Neuron' and 'Neuron' itself like this:++> nerve <- growNerve :: IO (Nerve (SequenceFromImpulse Int) AxonConductive (SequenceForImpulse Int) AxonNonConductive)+> neuron <- attach defaultOptions nerve++and for example print all 'Impulse's as they are coming in:++> print =<< getContentsFromNeuron nerve++Check 'growNeuron' for a more high-level function (of 'Incubation') which both 'grow's a 'Neuron' and corresponding 'Nerve' taking+care of all the details. Use this function only if you need decoupled 'grow'ing.+-}+growNerve :: (Impulse from, Impulse for, GrowAxon (Axon from fromConductivity), GrowAxon (Axon for forConductivity)) => IO (Nerve from fromConductivity for forConductivity)+growNerve = do+  from <- growAxon+  for <- growAxon+  return $ Nerve from for++{-|+Type which helps you define a type of the result of 'growNeuron'. It takes type of the 'Neuron' you want to 'grow' as an argument+and specifies a 'Nerve' which is conductive in both directions.+-}+type NerveBoth n = Incubation (Nerve (NeuronFromImpulse n) AxonConductive (NeuronForImpulse n) AxonConductive)+{-|+Type which helps you define a type of the result of 'growNeuron'. It takes type of the 'Neuron' you want to 'grow' as an argument+and specifies a 'Nerve' which is not conductive in any directions.+-}+type NerveNone n = Incubation (Nerve (NeuronFromImpulse n) AxonNonConductive (NeuronForImpulse n) AxonNonConductive)+{-|+Type which helps you define a type of the result of 'growNeuron'. It takes type of the 'Neuron' you want to 'grow' as an argument+and specifies a 'Nerve' which is conductive only in the direction from the 'Neuron'.+-}+type NerveOnlyFrom n = Incubation (Nerve (NeuronFromImpulse n) AxonConductive (NeuronForImpulse n) AxonNonConductive)+{-|+Type which helps you define a type of the result of 'growNeuron'. It takes type of the 'Neuron' you want to 'grow' as an argument+and specifies a 'Nerve' which is conductive only in the direction to the 'Neuron'.+-}+type NerveOnlyFor n = Incubation (Nerve (NeuronFromImpulse n) AxonNonConductive (NeuronForImpulse n) AxonConductive)++class (Typeable a, Eq a) => ChanClass a where+  neuronTypeOf :: a -> TypeRep++instance Impulse i => ChanClass (Chan i) where+  neuronTypeOf = head . typeRepArgs . head . typeRepArgs . typeOf -- we assume here that impulses are just NeuronFromImpulse or NeuronForImpulse++data ChanBox where+  ChanBox :: ChanClass a => a -> ChanBox++instance Eq ChanBox where+  ChanBox a == ChanBox b = typeOf a == typeOf b && cast a == Just b -- tests both typeOf and cast to be sure (cast could be defined to succeed for different types?)++getFromChan :: Nerve from fromConductivity for forConductivity -> [ChanBox]+getFromChan (Nerve (Axon c) _) = [ChanBox c]+getFromChan (Nerve NoAxon _) = []++{-|+Branches 'Nerve' on the 'Neuron' side. This allows multiple 'Neuron's to be attached to it and still receive all 'Impulse's+(otherwise just the first 'Neuron' which would read from a 'Nerve' would receive a given 'Impulse').+Only new 'Impulse's from a moment of branching on are conducted over new the branch, old 'Impulse's are not reconducted.+Branching can be applied multiple times.+-}+branchNerveFor :: Nerve from fromConductivity for AxonConductive -> IO (Nerve from fromConductivity for AxonConductive)+branchNerveFor (Nerve from (Axon c)) = do+  c' <- dupChan c+  return $ Nerve from (Axon c')++{-|+Branches 'Nerve' on the other (non-'Neuron') side. This allows using the same 'Nerve' at multiple parts of the network (program)+and still receive all 'Impulse's from 'Neuron' at all parts of the network (otherwise just the first read from a 'Nerve' would+receive a given 'Impulse').+Only new 'Impulse's from a moment of branching on are conducted over the new branch, old 'Impulse's are not reconducted.+Branching can be applied multiple times.+-}+branchNerveFrom :: Nerve from AxonConductive for forConductivity -> IO (Nerve from AxonConductive for forConductivity)+branchNerveFrom (Nerve (Axon c) for) = do+  c' <- dupChan c+  return $ Nerve (Axon c') for++{-|+Branches 'Nerve' on both sides. Same as both 'branchNerveFor' and 'branchNerveFrom'.+-}+branchNerveBoth :: Nerve from AxonConductive for AxonConductive -> IO (Nerve from AxonConductive for AxonConductive)+branchNerveBoth = branchNerveFrom >=> branchNerveFor
+ lib/Control/Etage/Internals.hs view
@@ -0,0 +1,118 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, FlexibleContexts, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, EmptyDataDecls, NamedFieldPuns #-}+{-# OPTIONS_GHC -fno-warn-orphans #-}++module Control.Etage.Internals (+  Axon(..),+  Nerve(..),+  Impulse(..),+  LiveNeuron(..),+  ImpulseValue,+  ImpulseTime,+  AxonConductive,+  AxonNonConductive,+  NeuronDissolved,+  NeuronId,+  waitForException+) where++import Control.Concurrent hiding (Chan)+import Data.Time.Clock.POSIX+import Data.Typeable+import Numeric+import Text.ParserCombinators.ReadP++import Control.Etage.Chan++-- TODO: Find better general representation for values (something analog to what a hologram is, so that it can be gradually simplified and gradually reconstructed). Could be an Incubation program itself?+-- | Type of a general representation of 'Impulse' values (data payload). Currently it is just a list of 'Rational' values.+type ImpulseValue = [Rational]++-- | Type of 'Impulse' timestamp. You can use 'getCurrentImpulseTime' for timestamp representing current time.+type ImpulseTime = POSIXTime++instance Read ImpulseTime where+  readsPrec _ r = do+    (time, sec) <- readFloat r+    ('s', rest) <- readP_to_S (char 's') sec+    return (time, rest)++{-|+Type class with common methods for impulses send over 'Nerve's and processed in 'Neuron's so that it is possible to define+'Neuron's which operate on any 'Impulse' type. An example of such 'Neuron' is "Control.Etage.Function".+-}+class (Show i, Typeable i) => Impulse i where+  -- | This method should return a timestamp when the 'Impulse' was created/finalized what should be the moment just before it is send over+  -- the 'Nerve'. So the moment it formed into its final form and started leaving the 'Neuron'.+  --+  -- Be careful that Haskell is a lazy language so such code:+  --+  -- > let v = lengthlyComputation+  -- > time <- getCurrentImpulseTime+  -- > sendFromNeuron nerve Value { impulseTimestamp = time, value = v }+  --+  -- will evaluate @v@ after 'getCurrentImpulseTime' call. You should make @v@ strict (using @BangPatterns@) like:+  --+  -- > let !v = lengthlyComputation+  impulseTime :: i -> ImpulseTime+  -- | This method should return all values (data payload) the 'Impulse' defines. Currently order and format is not yet finalized so+  -- it is just a list of 'Rational' values in some order (for now it probably should be the order in which the values are defined+  -- in the 'Impulse' constructor).+  --+  -- It is meant to allow general 'Neurons' which can work on any 'Impulse' type to be developed. For example 'Neuron's which+  -- implement some machine learning or data mining algorithms. It is on purpose that values are cleared of any semantic+  -- meaning so algorithms have better chance not to get in touch with some unintended domain specific knowledge.+  impulseValue :: i -> ImpulseValue++{-|+Is axon (one direction of a 'Nerve') conductive? Yes, it is.++This is type checked and enforced. If you define axon as conductive you have to make make sure that 'Impulse's send along it are+really read somewhere, otherwise a memory leak will occur.+-}+data AxonConductive deriving (Typeable)+{-|+Is axon (one direction of a 'Nerve') conductive? No, it is not.++This is type checked and enforced. It is useful to specify nonconductive axons when you are not interested in 'Impulse's from a+particular axon (direction), making sure there will not be a memory leak because 'Impulse's would pile up.+-}+data AxonNonConductive deriving (Typeable)++data Axon impulse conductivity where+  Axon :: Impulse i => Chan i -> Axon i AxonConductive+  NoAxon :: Axon i AxonNonConductive++{-|+Type representing a 'Nerve' between 'Neuron's. It is bi-directional (from and to a 'Neuron', each direction being one axon) and you+can specify type of 'Impulse's traveling along the axon and its conductivity (with AxonConductive or+AxonNonConductive).++You mostly do not need to specify this type manually if you are using 'growNeuron' and one of 'NerveBoth', 'NerveNone',+'NerveOnlyFrom' and 'NerveOnlyFor' types.+-}+data Nerve from fromConductivity for forConductivity where+  Nerve :: (Impulse from, Impulse for) => Axon from fromConductivity -> Axon for forConductivity -> Nerve from fromConductivity for forConductivity++deriving instance Typeable4 Nerve++instance (Typeable forConductivity, Typeable fromConductivity, Typeable from, Typeable for) => Show (Nerve from fromConductivity for forConductivity) where+  show = show . typeOf++type NeuronDissolved = SampleVar ()+type NeuronId = ThreadId++deriving instance Typeable1 SampleVar++instance Show NeuronDissolved where+  show = show . typeOf++{-|+Type representing a 'live' 'Neuron'.+-}+data LiveNeuron = LiveNeuron NeuronDissolved NeuronId deriving (Eq, Typeable)++instance Show LiveNeuron where+  show = show . typeOf++waitForException :: IO a+waitForException = newEmptyMVar >>= takeMVar
+ lib/Control/Etage/Propagate.hs view
@@ -0,0 +1,79 @@+{-# LANGUAGE TypeFamilies, GADTs, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, NamedFieldPuns #-}++module Control.Etage.Propagate (+  propagate,+  Translatable(..)+) where++import Control.Monad+import Data.Typeable++import Control.Etage.Internals+import Control.Etage.Externals++-- TODO: Implement delay in propagation (constant delay, random from some distribution)++data (Typeable from, Typeable for, Typeable forConductivity) => PropagateNeuron from for forConductivity = PropagateNeuron (PropagateOptions from for forConductivity) deriving (Typeable)++type PropagateFromImpulse from for forConductivity = NeuronFromImpulse (PropagateNeuron from for forConductivity)+type PropagateForImpulse from for forConductivity = NeuronForImpulse (PropagateNeuron from for forConductivity)+type PropagateOptions from for forConductivity = NeuronOptions (PropagateNeuron from for forConductivity)++{-|+Impulse instance for internal 'Neuron' which implements 'propagate'.+-}+instance (Typeable from, Typeable for, Typeable forConductivity) => Impulse (PropagateFromImpulse from for forConductivity) where+  impulseTime _ = undefined+  impulseValue _ = undefined++{-|+Impulse instance for internal 'Neuron' which implements 'propagate'.+-}+instance (Typeable from, Typeable for, Typeable forConductivity) => Impulse (PropagateForImpulse from for forConductivity) where+  impulseTime _ = undefined+  impulseValue _ = undefined++deriving instance Show (PropagateFromImpulse from for forConductivity)+deriving instance Show (PropagateForImpulse from for forConductivity)++{-|+An internal 'Neuron' which implements 'propagate'.+-}+instance (Typeable from, Typeable for, Typeable forConductivity) => Neuron (PropagateNeuron from for forConductivity) where+  data NeuronFromImpulse (PropagateNeuron from for forConductivity)+  data NeuronForImpulse (PropagateNeuron from for forConductivity)+  data NeuronOptions (PropagateNeuron from for forConductivity) = PropagateOptions {+      from :: Nerve from AxonConductive for forConductivity,+      for ::[Translatable from]+    }+  +  mkDefaultOptions = return PropagateOptions {+      from = undefined,+      for = undefined+    }+  +  grow options = return $ PropagateNeuron options+  +  live _ (PropagateNeuron PropagateOptions { from, for }) = forever $ do+    i <- getFromNeuron from+    mapM_ (\(Translatable n) -> translateAndSend n i) for++{-|+It 'grow's an internal 'Neuron' which propagates 'Impulse's from a given 'Nerve' to other 'Nerve's, 'translate'-ing as necessary.++Check 'attachTo' for a more high-level function (of 'Incubation') taking care of all the details (like branching 'Nerve's as necessary).+Use this function only if you are dealing with 'grow'ing and 'attach'ing of 'Nerve's directly.+-}+propagate :: forall from for forConductivity. (Typeable from, Typeable for, Typeable forConductivity) => Nerve from AxonConductive for forConductivity -> [Translatable from] -> IO ()+propagate from for = do+  -- we do not manage this neuron, it will be cleaned by RTS at program exit+  -- TODO: What if this is not the only thing the program is doing? Should we cleanup this threads at the end of Incubation, too?+  _ <- attach (\o -> o { from, for } :: NeuronOptions (PropagateNeuron from for forConductivity)) undefined+  return ()++{-|+An existentially quantified types encompassing all 'Nerve's which can be 'translate'd from the same 'Impulse' type. Used in 'attachTo'+(and 'propagate') to list all 'Nerve's to which you want a given 'Nerve' to 'attach' to (and 'Impulse's to 'propagate').+-}+data Translatable i where+  Translatable :: ImpulseTranslator i for => Nerve from fromConductivity for AxonConductive -> Translatable i
+ lib/Control/Etage/Sequence.hs view
@@ -0,0 +1,103 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, DeriveDataTypeable, TypeSynonymInstances, StandaloneDeriving, NamedFieldPuns #-}++{-|+This module defines a 'Neuron' which generates values based on a given sequence at a given interval.+If it reaches the end of a sequence it initiates 'dissolving'.+You 'grow' default version of it, which gives you an infinite source of random 'Int's at random interval of maximum length of 1+second, in 'Incubation' by using something like:++> nerveRandom <- growNeuron defaultOptions :: NerveOnlyFrom (SequenceNeuron Int)++or for an infinite source of ones with same random interval:++> nerveOnes <- growNeuron (\o -> o { valueSource = repeat 1 }) :: NerveOnlyFrom (SequenceNeuron Int)++It is an example of a 'Neuron' with a parametrized type.+-}++module Control.Etage.Sequence (+  SequenceNeuron,+  SequenceFromImpulse,+  SequenceForImpulse,+  SequenceOptions,+  NeuronFromImpulse(..),+  NeuronForImpulse,+  NeuronOptions(..)+) where++import Control.Concurrent+import Control.Monad+import Data.Typeable+import System.Random++import Control.Etage++defaultMaxInterval :: Int+defaultMaxInterval = 1000000 -- microseconds, 1 second++data (Real r, Random r, Show r, Typeable r) => SequenceNeuron r = SequenceNeuron (SequenceOptions r) deriving (Typeable)++instance Typeable r => Show (SequenceNeuron r) where+  show = show . typeOf++{-|+'Impulse's from 'SequenceNeuron'. This 'Impulse' constructor is defined:++[@Value { impulseTimestamp :: 'ImpulseTime', value :: 'Rational' }@]+@impulseTimestamp@ is time when the value was send, @value@ contains the value.+-}+type SequenceFromImpulse r = NeuronFromImpulse (SequenceNeuron r)+-- | 'Impulse's for 'SequenceNeuron'. This 'Neuron' does not define any 'Impulse's it would receive.+type SequenceForImpulse r = NeuronForImpulse (SequenceNeuron r)+{-|+Options for 'SequenceNeuron'. This options are defined:++[@valueSource :: \[r\]@] The list of values to send. If the end of the list is reached, 'Neuron' initiates 'dissolving'. Default+is an infinite list of values of type @r@ generated by the 'StdGen' random generator.++[@intervalSource :: \['Int'\]@] The list of intervals between values. It is defined as a delay in microseconds before the next value+is send. If the end of the list is reached, 'Neuron' initiates 'dissolving'. Default is a list of random delays with maximum+length of 1 second generated by the 'StdGen' random generator.+-}+type SequenceOptions r = NeuronOptions (SequenceNeuron r)++-- | Impulse instance for 'SequenceNeuron'.+instance (Real r, Random r, Show r, Typeable r) => Impulse (SequenceFromImpulse r) where+  impulseTime Value { impulseTimestamp } = impulseTimestamp+  impulseValue Value { value } = [toRational value]++-- | Impulse instance for 'SequenceNeuron'.+instance (Real r, Random r, Show r, Typeable r) => Impulse (SequenceForImpulse r) where+  impulseTime _ = undefined+  impulseValue _ = undefined++deriving instance Show (SequenceForImpulse r)++-- | A 'Neuron' which generates values based on a given sequence at a given interval.+instance (Real r, Random r, Show r, Typeable r) => Neuron (SequenceNeuron r) where+  data NeuronFromImpulse (SequenceNeuron r) = Value {+      impulseTimestamp :: ImpulseTime, -- time is first so that ordering is first by time+      value :: r+    } deriving (Eq, Ord, Read, Show)+  data NeuronForImpulse (SequenceNeuron r)+  data NeuronOptions (SequenceNeuron r) = SequenceOptions {+      valueSource :: [r],+      intervalSource :: [Int] -- microseconds+    } deriving (Eq, Ord, Read, Show)+  +  mkDefaultOptions = do+    generator <- newStdGen+    generator' <- newStdGen+    return SequenceOptions {+        valueSource = randoms generator,+        intervalSource = randomRs (0, defaultMaxInterval) generator'+      }+  +  grow options = return $ SequenceNeuron options+  +  live nerve n@(SequenceNeuron SequenceOptions { valueSource, intervalSource }) = do+    forM_ (zip valueSource intervalSource) $ \(v, i) -> do+      threadDelay i+      time <- getCurrentImpulseTime+      sendFromNeuron nerve $ Value time v+    dissolving n
+ lib/Control/Etage/Timeout.hs view
@@ -0,0 +1,76 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, DeriveDataTypeable, TypeSynonymInstances, StandaloneDeriving, NamedFieldPuns #-}++{-|+This module defines a simple 'Neuron' which initiates 'dissolving' after a given delay. It can be used to limit execution time of+the network. You 'grow' it in 'Incubation' by using something like:++> _ <- growNeuron defaultOptions :: NerveNone TimeoutNeuron++somewhere among (best at the end) 'growNeuron' calls for other 'Neuron's in 'Incubation'.+-}++module Control.Etage.Timeout (+  TimeoutNeuron,+  TimeoutFromImpulse,+  TimeoutForImpulse,+  TimeoutOptions,+  NeuronFromImpulse,+  NeuronForImpulse,+  NeuronOptions(..)+) where++import Control.Concurrent+import Control.Monad+import Data.Typeable++import Control.Etage++defaultTimeout :: Int+defaultTimeout = 60000000 -- microseconds, 60 seconds++data TimeoutNeuron = TimeoutNeuron TimeoutOptions deriving (Typeable)++instance Show TimeoutNeuron where+  show = show . typeOf++-- | 'Impulse's from 'TimeoutNeuron'. This 'Neuron' does not define any 'Impulse's it would send.+type TimeoutFromImpulse = NeuronFromImpulse TimeoutNeuron+-- | 'Impulse's for 'TimeoutNeuron'. This 'Neuron' does not define any 'Impulse's it would receive.+type TimeoutForImpulse = NeuronForImpulse TimeoutNeuron+{-|+Options for 'TimeoutNeuron'. This option is defined:++[@timeout :: 'Int'@] The length of the delay in microseconds before initiating 'dissolving'. Default is 60 seconds.+-}+type TimeoutOptions = NeuronOptions TimeoutNeuron++-- | Impulse instance for 'TimeoutNeuron'.+instance Impulse TimeoutFromImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++-- | Impulse instance for 'TimeoutNeuron'.+instance Impulse TimeoutForImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++deriving instance Show TimeoutFromImpulse+deriving instance Show TimeoutForImpulse++-- | A simple 'Neuron' which initiates 'dissolving' after a given delay.+instance Neuron TimeoutNeuron where+  data NeuronFromImpulse TimeoutNeuron+  data NeuronForImpulse TimeoutNeuron+  data NeuronOptions TimeoutNeuron = TimeoutOptions {+      timeout :: Int -- microseconds+    } deriving (Eq, Ord, Read, Show)+  +  mkDefaultOptions = return TimeoutOptions {+      timeout = defaultTimeout+    }+  +  grow options = return $ TimeoutNeuron options+  +  live _ n@(TimeoutNeuron TimeoutOptions { timeout }) = do+    threadDelay timeout+    dissolving n
+ lib/Control/Etage/Worker.hs view
@@ -0,0 +1,88 @@+{-# LANGUAGE TypeFamilies, MultiParamTypeClasses, GADTs, FlexibleInstances, ScopedTypeVariables, TypeSynonymInstances, StandaloneDeriving, DeriveDataTypeable, EmptyDataDecls, NamedFieldPuns #-}++{-|+This module defines a worker 'Neuron' which evaluates 'IO' actions it receives. It is useful to offload lengthly 'IO' actions+into another thread. In the case of too many queued 'IO' actions they are silently dropped and only newest ones are evaluated.+You 'grow' it in 'Incubation' by using something like:++> nerveWorker <- growNeuron defaultOptions :: NerveOnlyFor WorkerNeuron+-}++module Control.Etage.Worker (+  WorkerNeuron,+  WorkerFromImpulse,+  WorkerForImpulse,+  WorkerOptions,+  NeuronFromImpulse,+  NeuronForImpulse(..),+  NeuronOptions(..),+  WorkType+) where++import Control.Applicative+import Control.Monad+import Data.Typeable++import Control.Etage++-- | Type of work this worker 'Neuron' evaluates.+type WorkType = IO ()++instance Show WorkType where+  show = show . typeOf++-- TODO: We could maybe send results back?++data WorkerNeuron deriving (Typeable)++-- | 'Impulse's from 'WorkerNeuron'. This 'Neuron' does not define any 'Impulse's it would send.+type WorkerFromImpulse = NeuronFromImpulse WorkerNeuron+{-|+'Impulse's for 'WorkerNeuron'. This 'Impulse' constructor is defined:++[@Work { impulseTimestamp :: ImpulseTime, work :: WorkType }@]+@impulseTimestamp@ is time when the action was enqueued for evaluation in the 'WorkerNeuron', @work@ is enqueued action.+-}+type WorkerForImpulse = NeuronForImpulse WorkerNeuron+{-|+Options for 'WorkerNeuron'. This option is defined:++[@mapOnCapability :: 'NeuronMapCapability'@] How to map the 'Neuron' on capabilities (OS threads). With this option you can fix+multiple 'Neuron's on the same capability (for example, by generating one value with 'mkNeuronMapOnRandomCapability' and using+it for all those 'Neuron's) which is sometimes necessary when dealing with external (FFI) libraries. Default value+is 'NeuronFreelyMapOnCapability'.+-}+type WorkerOptions = NeuronOptions WorkerNeuron++-- | Impulse instance for 'WorkerNeuron'.+instance Impulse WorkerFromImpulse where+  impulseTime _ = undefined+  impulseValue _ = undefined++-- | Impulse instance for 'WorkerNeuron'.+instance Impulse WorkerForImpulse where+  impulseTime Work { impulseTimestamp } = impulseTimestamp+  impulseValue _ = []++deriving instance Show WorkerFromImpulse++-- | A worker 'Neuron' which evaluates 'IO' actions it receives.+instance Neuron WorkerNeuron where+  data NeuronFromImpulse WorkerNeuron+  data NeuronForImpulse WorkerNeuron = Work {+      impulseTimestamp :: ImpulseTime,+      work :: WorkType+    } deriving (Show)+  data NeuronOptions WorkerNeuron = WorkerOptions {+      mapOnCapability :: NeuronMapCapability+    } deriving (Eq, Ord, Read, Show)+  +  mkDefaultOptions = return WorkerOptions {+      mapOnCapability = NeuronFreelyMapOnCapability+    }+  +  getNeuronMapCapability WorkerOptions { mapOnCapability } = mapOnCapability+  +  live nerve _ = forever $ do+    Work { work } <- head <$> waitAndSlurpForNeuron nerve -- just newest+    work