Giter Club home page Giter Club logo

eve's Introduction

Eve

Join the chat at https://gitter.im/eve-framework/Lobby Hackage

An extensible event-driven application framework in haskell for building embarassingly modular software.

Documentation

You can find hackage documentation for eve HERE

Getting started

Here's a guide which walks you through building your first application in Eve from start to finish, it's quite thorough and it's a great place to start!

If you have any issues (and I'm sure there'll be a few; it's a new project!) please report them here.

Core Principles

Eve's core principle is making it easy to build programs in a modular way. There are two key concepts in Eve which you should be aware of:

  • Events
  • State

Events

Eve provides many useful combinators for dispatching events and adding listeners to events, events are a broad concept in Eve and can be triggered by user-interaction, file-changes, even network sockets! Anything you can think of really! Each time an event is fired, your app 'reacts' by running any associated listeners on the given event.

The functions you need to know are (with simplified types, see the real type in the hackage docs):

  • dispatchEvent :: forall eventType result m. (Monad m, Monoid result) => eventType -> m result
  • addListener :: forall eventType result m. (Monad m, Monoid result) (eventType -> m result) -> m ListenerId

As I mention above, these types are simplified a bit (and yet they still look complicated!). Actually, the types look so complex so that they're simpler to use! The forall makes it so that you can call dispatchEvent with ANY Typeable type and it will run the proper event listeners which were registered by addListener; those listeners can alter app state, or even dispatch more events! If the listeners return some (monoidal) value then the results from all listeners are combined with mappend and are returned. That's pretty much it!

Here's a quick example for those who need to see some code:

import Eve
import Data.Monoid

-- Define an event to listen for, in this case we don't even need any data alongside it.
data ComputeScore = ComputeScore

-- Define some computations which calculate some aspect of score.
-- We accept an argument of 'ComputeScore' to define what this is a listener for
scoreContributor1, scoreContributor2 :: ComputeScore -> App (Sum Int)
scoreContributor1 _ = do
  ... -- do some calculation over app state to determine one aspect of score
  return (Sum score)

scoreContributor2 _ = do
  ... -- Calculate some other aspect of the score
  return (Sum score)

-- In eve's initialization block we register the listeners, we could add these listeners anywhere
main :: IO ()
main = eve_ $ do
  ... -- other initialization (e.g. key listeners, etc.)
  addListener_ scoreContributor1
  addListener_ scoreContributor2

  -- This dispatches the triggering event and monoidally sums all the individual score components!
computeTotalScore :: App (Sum Int)
computeTotalScore = do
  Sum score <- dispatchEvent ComputeScore
  return score

State

Next we see how Eve handles state. Eve seeks to be as extensible as possible so it makes very few assumptions about the type of state that you (or your extensions) plan to store. You can define a type of state yourself using data and then provide actions which alter that state using a MonadState instance (from mtl). Don't worry if you don't know what that means, here's a real quick example which uses the combinators from the lens library to make a few simple state changes.

import Eve
import Control.Lens
data MyState = MyState
  { _myInt :: Int
  , _myString :: String
  }
makeLenses ''MyState

-- This alters some state and returns the old string for some reason.
doSomething :: Action MyState String
doSomething = do
  oldString <- use myString
  myString .= "Hi!"
  myInt += 1
  return oldString

So what does this gain us? Well now if we have a MyState somewhere in our app we can run that Action on it! We can also register that Action as a listener for some event!

Now for the interesting part; handling state for extensions. This is usually a bit tricky since the types that an extension might use aren't known by you (the app author). Eve takes care of this by providing an interface for extensions to store and keep track of arbitrary types, while still allowing other extensions to run actions that it exports. This is where the HasStates typeclass comes in; here's the honest to goodness implementation:

class HasStates s  where
  states :: Lens' s States

If your state implements that typeclass, then extensions can store their own states inside it! It's pretty easy to implement too, let's add it to our MyState.

import Eve
import Control.Lens
data MyState = MyState
  { _myInt :: Int
  , _myString :: String
  , _myStates :: States
  }
makeLenses ''MyState

instance HasStates MyState where
    states = myStates

Done! We added a new field which has the type States which is exported by Eve. Then we just took the lens created by makeLenses and used it in our instance. That's it! Now extensions can store their own state inside Action MyState by using the stateLens; check out the hackage docs on that for more info on how to do it!

Those are the basics, but you can do much more than that if you like! Eve also lets you add listeners and dispatch events on an Object specific basis! If you have a copy of some state (let's say a single instance of an Enemy in a game) you can dispatch events over that enemy individually and any registered (Action Enemy) callbacks will be run without affecting any other enemies! Check out HasEvents to see how that works.

One last cool feature is that event listeners can return information! If your event listener results in a return value that's a Monoid (like a list, or string for example) you can collect the responses of all the listeners when you call dispatchEvent. This is a great way for your application to 'ask' extensions about their state.

When designing applications in Eve; it's crucial to think about how the state of you application will be stored, and how different components interact. Eve works best when components are separated and communicate with each-other through events. This is because it allows those who will eventually write extensions to your application to 'hook' into those events to add functionality.

There are some definite Pros and Cons to Eve's approach:

Pros

  • Implementing most core functionality using the event system your app remains extensible.
  • Flexibility & Adaptability; applications can be written in such a way that users can replace entire components with alternate versions.

Cons

  • Module cross-dependencies makes the community infrastructure more fragile,
  • This architecture takes some getting used-to.

Contributing

Installation

Eve uses Stack for reproducible builds.

  1. Install stack
  2. Clone this repo and cd into the directory
  3. Run stack build

Running Tests

  • stack test

Contributions

Chatting about features is a key part of Eve's development; come join us in the Chat Room to discuss features or improvements!

Related Works

eve's People

Contributors

chrispenner avatar xaverdh avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar

eve's Issues

Proposed solution to type mismatch for makeStateLens in lts-19.19 (GHC 9.0.2)

I tried compiling on Stackage lts-19.19, and found the following type error for makeStateLens:

     Couldn't match type: a0 -> a1 -> f0 a1
                     with: forall (f :: * -> *).
                           Functor f =>
                           (a -> f a) -> myState -> f myState
      Expected: Lens' myState a -> Lens' s a
        Actual: (a0 -> a1 -> f0 a1) -> a0 -> e0 -> f0 e0
     In the expression: stateLens .
      In an equation for makeStateLens’: makeStateLens = (stateLens .)

This seems to be very similar to the first of the errors in issue #6.

After playing with the types and looking up the recent changes to GHC, I found a way to resolve this error. It seems that the cause is that GHC >=9.0 has more restrictive conditions for matching non-top-level forall, as documented in the section on "Subsumption" in the GHC User's Guide.

This documentation says that the error can be resolved by eta-expansion; that is, change the function definition from point-free style:

makeStateLens = (stateLens .)

to pointful style:

makeStateLens x = (stateLens .) x

I tried this and it works. The good news is that there were no other errors compiling with lts-19.19 (GHC 9.0.2). (There was one warning -- that in src/Eve/Internal/Actions.hs, the import of 'Data.Semigroup' is redundant.) Would a pull request be useful? The only edit required is the one-line change to makeStateLens shown above.

Apparently eta-expansion may degrade performance in some cases. However, there is probably no other easy solution. Perhaps using some kind of type wrangling to convince the type checker would work, but I have no idea whether this is possible.

Compile errors

On http://104.239.175.197:8080/package/eve there's currently compile failures for GHC 7.10 & GHC 7.8:

Configuring component lib from eve-0.1.6...
Preprocessing library eve-0.1.6...
[1 of 9] Compiling Eve.Internal.Events ( src/Eve/Internal/Events.hs, /tmp/matrix-worker/1489628429/dist-newstyle/build/x86_64-linux/ghc-7.10.3/eve-0.1.6/build/Eve/Internal/Events.o )
[2 of 9] Compiling Eve.Internal.States ( src/Eve/Internal/States.hs, /tmp/matrix-worker/1489628429/dist-newstyle/build/x86_64-linux/ghc-7.10.3/eve-0.1.6/build/Eve/Internal/States.o )

src/Eve/Internal/States.hs:85:18:
    Couldn't match type ‘(a -> f a) -> a0 -> f a0’
                   with ‘forall (f1 :: * -> *).
                         Functor f1 =>
                         (a -> f1 a) -> myState -> f1 myState’
    Expected type: Lens' myState a -> (a -> f a) -> s -> f s
      Actual type: ((a -> f a) -> a0 -> f a0) -> (a -> f a) -> s -> f s
    Relevant bindings include
      makeStateLens :: Lens' myState a -> Lens' s a
        (bound at src/Eve/Internal/States.hs:85:1)
    In the expression: (stateLens .)
    In an equation for ‘makeStateLens’: makeStateLens = (stateLens .)

And previous releases wouldn't build with GHC 7.8 because of

Configuring component lib from eve-0.1.2...
Preprocessing library eve-0.1.2...
[1 of 9] Compiling Eve.Internal.Events ( src/Eve/Internal/Events.hs, /tmp/matrix-worker/1489628453/dist-newstyle/build/x86_64-linux/ghc-7.8.4/eve-0.1.2/build/Eve/Internal/Events.o )
[2 of 9] Compiling Eve.Internal.States ( src/Eve/Internal/States.hs, /tmp/matrix-worker/1489628453/dist-newstyle/build/x86_64-linux/ghc-7.8.4/eve-0.1.2/build/Eve/Internal/States.o )
[3 of 9] Compiling Eve.Internal.Actions ( src/Eve/Internal/Actions.hs, /tmp/matrix-worker/1489628453/dist-newstyle/build/x86_64-linux/ghc-7.8.4/eve-0.1.2/build/Eve/Internal/Actions.o )

src/Eve/Internal/Actions.hs:32:22:
    Not in scope: type constructor or class ‘Applicative’

src/Eve/Internal/Actions.hs:38:24:
    Not in scope: type constructor or class ‘Applicative’

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.