Auto
Haskell DSL and platform providing denotational, compositional api for discrete-step, locally stateful, interactive programs, games & automations. http://hackage.haskell.org/package/auto
Install / Use
/learn @mstksg/AutoREADME
Auto
$ cabal install auto
Check it out!
-- Let's implement a PID feedback controller over a black box system.
import Control.Auto
import Prelude hiding ((.), id)
-- We represent a system as `System`, an `Auto` that takes stream of `Double`s
-- as input and transforms it into a stream of `Double`s as output. The `m`
-- means that a `System IO` might do IO in the process of creating its ouputs,
-- for instance.
--
type System m = Auto m Double Double
-- A PID controller adjusts the input to the black box system until the
-- response matches the target. It does this by adjusting the input based on
-- the current error, the cumulative sum, and the consecutive differences.
--
-- See http://en.wikipedia.org/wiki/PID_controller
--
-- Here, we just lay out the "concepts"/time-varying values in our system as a
-- recursive/cyclic graph of dependencies. It's a feedback system, after all.
--
pid :: MonadFix m => (Double, Double, Double) -> System m -> System m
pid (kp, ki, kd) blackbox = proc target -> do -- proc syntax; see tutorial
rec -- err :: Double
-- the difference of the response from the target
let err = target - response
-- cumulativeSum :: Double
-- the cumulative sum of the errs
cumulativeSum <- sumFrom 0 -< err
-- changes :: Maybe Double
-- the consecutive differences of the errors, with 'Nothing' at first.
changes <- deltas -< err
-- adjustment :: Double
-- the adjustment term, from the PID algorithm
let adjustment = kp * err
+ ki * cumulativeSum
+ kd * fromMaybe 0 changes
-- the control input is the cumulative sum of the adjustments
control <- sumFromD 0 -< adjustment
-- the response of the system, feeding the control into the blackbox
response <- blackbox -< control
-- the output of this all is the value of the response
id -< response
What is it?
Auto is a Haskell DSL and platform providing an API with declarative, compositional, denotative semantics for discrete-step, locally stateful, interactive programs, games, and automations, with implicitly derived serialization.
It is suited for any domain where your program's input or output is a stream of values, input events, or output views. At the high-level, it allows you to describe your interactive program or simulation as a value stream transformer, by composition and transformation of other stream transformers. So, things like:
- Chat bots
- Turn-based games
- GUIs
- Numerical simulations
- Process controllers
- Text-based interfaces
- (Value) stream transformers, filters, mergers, processors
It's been called "FRP for discrete time contexts".
Intrigued? Excited? Start at the tutorial!
It's a part of this package directory and also on github at the above link. The current development documentation server is found at https://mstksg.github.io/auto.
From there, you can check out my All About Auto series on my blog, where I break sample projects and show to approach projects in real life. You can also find examples and demonstrations in the auto-examples repo on github.
Buzzwords explained!
-
Haskell DSL/library: It's a Haskell library that provides a domain-specific language for composing and declaring your programs/games.
Why Haskell? Well, Haskell is one of the only languages that has a type system expressive enough to allow type-safe compositions without getting in your way. Every composition and component is checked at compile-time to make sure they even make sense, so you can work with an assurance that everything fits together in the end --- and also in the correct way. The type system can also guide you in your development as well. All this without the productivity overhead of explicit type annotations. In all honesty, it cuts the headache of large projects down --- and what you need to keep in your head as you develop and maintain --- by at least 90%.
-
Platform: Not only gives the minimal tools for creating your programs, but also provides a platform to run and develop and integrate them, as well as many library/API functions for common processes.
-
Declarative: It's not imperative. That is, unlike in other languages, you don't program your program by saying "this happens, then this happens...and then in case A, this happens; in case B, something else happens". Instead of specifying your program/game by a series of state-changing steps and procedures (a "game loop"), you instead declare "how things are". You declare fixed or evolving relationships between entities and processes and interactions. And this declaration process is high-level and pure.
-
Denotative: Instead of your program being built of pieces that change things and execute things sequentially, your entire program is composed of meaningful semantic building blocks that "denote" constant relationships and concepts. The composition of such building blocks also denote new concepts. Your building blocks are well-defined ideas.
-
Compositional: You build your eventually complex program/game out of small, simple components. These simple components compose with each other; and compositions of components compose as well with other components. Every "layer" of composition is seamless. It's the scalable program architecture principle in practice: If you combine an A with an A, you don't get a B; you get another A, which can combine with any other A.
Like unix pipes, where you can build up complex programs by simply piping together simple, basic ones.
-
Discrete-step: This library is meant for things that step discretely; there is no meaningful concept of "continuous time". Good examples include turn-based games, chat bots, and cellular automata; bad examples include real-time games and day trading simulations.
-
Locally stateful: Every component encapsulates its own local (and "hidden") state. There is no global or impicitly shared state. This is in contrast to those "giant state monad" libraries/abstractions where you carry around the entire game/program state in some giant data type, and have your game loop simply be an update of that state.
If you have a component representing a player, and a component representing an enemy --- the two components do not have to ever worry about the state of the other, or the structure of their shared state.
Also, you never have to worry about something reading or modifying a part of the shared/global state it wasn't meant to read or modify! (Something you cannot guaruntee in the naive implementatation of the "giant state monad" technique).
-
Interactive: The behavior and structure of your program can respond and vary dynamically with outside interaction. I'm not sure how else to elaborate on the word "interactive", actually!
-
Interactive programs, games and automations: Programs, games, and automations/simulations. If you're making anything discrete-time that encapsulates some sort of internal state, especially if it's interactive, this is for you!! :D
-
Implicitly derived serialization: All components and their compositions by construction are automatically "freezable" and serializable, and re-loaded and resumed with all internal state restored. As it has been called by ertes, it's "save states for free".
Support
The official support and discussion channel is #haskell-auto on freenode. You can also usually find me (the maintainer and developer) as jle` on #haskell-game or #haskell. There's also a gitter channel if IRC is not your cup of tea. Also, contributions to documentation and tests are welcome! :D
Why Auto?
Auto is distinct from a "state transformer" (state monad, or explicit state passing) in that it gives you the ability to implicitly compose and isolate state transformers and state.
That is, imagine you have two different state monads with different states, and you can compose them together into one giant loop, and:
-
You don't have to make a new "composite type"; you can add a new component dealing with its own state without changing the total state type.
-
You can't write anything cross-talking. You can't write anything that can interfere with the internal state of any components; each one is isolated.
So --- Auto is useful over a state monad/state transformer approach in cases where you like to build your problem out of multiple individual components, and compose them all together at once.
Examples include a multiple-module stateful chat bot, where every module of the chat bot consists of its
