All Projects → typedbyte → effet

typedbyte / effet

Licence: BSD-3-Clause license
An effect system based on type classes, written in Haskell.

Programming Languages

haskell
3896 projects

Projects that are alternatives of or similar to effet

Extensible Effects
Extensible Effects: An Alternative to Monad Transformers
Stars: ✭ 167 (+363.89%)
Mutual labels:  effects
Axanimationchain
AXAnimationChain is a chain animation library, can be used to easily create CAAnimation based chain animation. There are two kinds of combination chain, one is called combination, the other is called link, created by the two ways above, the animation can be carried out at the same time, can also according to the time order, you can use the code to create a rich and complex animation.
Stars: ✭ 234 (+550%)
Mutual labels:  effects
unity-reaction-diffusion
(WIP) ✏️ Test of a Reaction-Diffusion simulated with a compute shader in Unity.
Stars: ✭ 25 (-30.56%)
Mutual labels:  effects
View Effects
Apply custom effects on view backgrounds
Stars: ✭ 176 (+388.89%)
Mutual labels:  effects
Swiftuiimageeffects
Swift port of Apple UIImage+UIImageEffecs category.
Stars: ✭ 213 (+491.67%)
Mutual labels:  effects
my-notes
工程师的自我修养
Stars: ✭ 28 (-22.22%)
Mutual labels:  effects
Unityskidmarks
A simple skidmark effect generator for Unity3D
Stars: ✭ 165 (+358.33%)
Mutual labels:  effects
Messier87
A realtime raytracing blackhole renderer
Stars: ✭ 53 (+47.22%)
Mutual labels:  effects
Giftsurfaceview
🌷 直播间送礼物拼图案动画控件
Stars: ✭ 228 (+533.33%)
Mutual labels:  effects
SoxSharp
.NET wrapper for SoX.
Stars: ✭ 41 (+13.89%)
Mutual labels:  effects
Capability
Extensional capabilities and deriving combinators
Stars: ✭ 176 (+388.89%)
Mutual labels:  effects
Redis4cats
🔖 Redis client built on top of Cats Effect, Fs2 and Lettuce
Stars: ✭ 189 (+425%)
Mutual labels:  effects
UnityGUI
UGUI Panel Systems for navigation, animation and more
Stars: ✭ 80 (+122.22%)
Mutual labels:  effects
Raj
The Elm Architecture for JavaScript
Stars: ✭ 169 (+369.44%)
Mutual labels:  effects
jeff
Effects for Java
Stars: ✭ 14 (-61.11%)
Mutual labels:  effects
Toucheffects
Android View点击特效TouchEffects,几行代码为所有控件添加点击效果
Stars: ✭ 167 (+363.89%)
Mutual labels:  effects
freedsl
Practical effect composition library based on abstract wrapping type and the free monad
Stars: ✭ 37 (+2.78%)
Mutual labels:  effects
habbo-downloader
⚡A tiny script to download various files directly from Habbo.
Stars: ✭ 47 (+30.56%)
Mutual labels:  effects
comfy-scenes
A rudimentary app for interactive Twitch scenes using Vue.js. It monitors your Twitch channel chat for !commands using Comfy.js (by instafluff and others), plays mp3 files, loads images, and interacts with Vue.js components.
Stars: ✭ 24 (-33.33%)
Mutual labels:  effects
xamarin-forms-statusbar
Xamarin.Forms Effect to manage the StatusBar BackgroundColor.
Stars: ✭ 16 (-55.56%)
Mutual labels:  effects

effet

Hackage

Overview

effet is an effect system based on type classes, written in Haskell. A central idea of an effect system is to track effects (like performing a file system access) on the type level in order to describe the behavior of functions more precisely. Another central idea of an effect system is the well-known design principle of separating an interface (the effect) from its actual implementation (the effect handler or effect interpreter). Hence, effet allows developers to write programs by composing functions which describe their effects on the type level, and to provide different implementation strategies for those effects when running the programs.

effet is by far not the first library which pursues these ideas. It borrows various ideas from existing libraries, just to name a few:

  • It is based on type classes, like mtl and fused-effects.
  • It is based on plucking constraints, like mtl, but without the "n² instances problem".
  • It supports TemplateHaskell-based generation of the effect infrastructure, like polysemy.
  • It supports functional dependencies and tagged effects for effect disambiguation, like ether.

effet has a rather down-to-earth implementation without much magic. It is like a thin wrapper around the transformers library and its lifting friends transformers-base and monad-control, thus minimizing the reinvention of the wheel. The library and its documentation can be found on Hackage.

Quick-Start Guide

Examples can be found in the examples folder. The following sections give a quick overview of the most important features.

Defining Effects

When defining effects or effect handlers, the module Control.Effect.Machinery provides everything we need:

import Control.Effect.Machinery

Effects are ordinary type classes, like the following:

class Monad m => FileSystem' tag m where
  readFile'  :: FilePath -> m String
  writeFile' :: FilePath -> String -> m ()

Note the tag type parameter. Such a parameter is used to disambiguate effects of the same type, i.e. we can use multiple FileSystem effects in our program simultaneously. In effet, the naming convention is to use an apostrophe as name suffix whenever we use tags. However, effet is not limited to that, we can easily define our effect without a tag parameter:

class Monad m => FileSystem m where
  readFile  :: FilePath -> m String
  writeFile :: FilePath -> String -> m ()

Let's go on with the tagged version for now and pretend that the untagged type class does not exist. The next step is to generate the effect handling, lifting and tagging infrastructure for our new effect using the TemplateHaskell language extension:

makeTaggedEffect ''FileSystem'

This line generates the necessary infrastructure for combining our new effect with other effects. We also get the untagged version of the effect for free, i.e. the corresponding untagged definitions (FileSystem, readFile, writeFile, note the missing apostrophes) are generated for us. This line also generates functions which help us to tag (tagFileSystem'), retag (retagFileSystem') and untag (untagFileSystem') our effect. More on that later.

effet also provides the function makeTaggedEffectWith to define our own naming convention if we don't like the apostrophes. For untagged effects, we would have simply used the function makeEffect instead.

Using Effects

We can now use our effect to write programs that access the file system. We will define a simple program which appends something to a file. In order to make it more interesting, we will combine it with the pre-defined Writer effect to report the file size before and after appending, just to demonstrate the interplay with other effects:

import Control.Effect.Writer
program :: (FileSystem m, Writer [Int] m) => FilePath -> String -> m ()
program path txt = do
  content <- readFile path
  let size = length content
  tell [size]
  seq size $ writeFile path (content ++ txt)
  tell [size + length txt]

In order to run this program, we need a handler (or "interpreter") for our effect. There are several ways to interpret our effect. We could, for example, really access our local file system or just provide a virtual, in-memory file system. Hence, we will define two effect handlers for demonstration purposes.

Defining Effect Handlers

First of all, we could – in theory – interpret effects without using effet at all, which is something that is not possible in many other effect systems. Since effects are ordinary type classes, we would just instantiate the monad type m with a type that provides instances for all (!) the effect type classes that constrain m. This is where the so-called "n² instances problem" of mtl comes from, since every effect handler that wants to handle a single effect (i.e., that wants to provide a type class instance for the effect it wants to handle) must also provide type class instances for all other effects – some of which might not even be known at compile-time – in order to delegate them to their corresponding effect handlers.

The advantage of effet is the ability to interpret effects separately, one by one, by plucking constraints and not caring about all effects at the same time. In effet, we write an effect handler by defining a type and a corresponding type class instance for the effect we are interested in and let the infrastructure of effet handle the delegation of all other effects to their corresponding effect handlers.

An effect handler is a monad transformer which provides an instance for our effect type class. First, let's define the handler type for the local file system handler. We can derive all the necessary instances for proper effect lifting, some of them by using the DerivingVia language extension:

newtype LocalFS m a =
  LocalFS { runLocalFS :: m a }
    deriving (Applicative, Functor, Monad, MonadIO)
    deriving (MonadTrans, MonadTransControl) via IdentityT
    deriving (MonadBase b, MonadBaseControl b)

Next, we provide the instance for our FileSystem' effect. We need another import for that:

import qualified System.IO as IO
instance MonadIO m => FileSystem' tag (LocalFS m) where
  readFile' path = liftIO $ IO.readFile path
  writeFile' path txt = liftIO $ IO.writeFile path txt

Note that we do not need a separate instance for the untagged version of our effect which was generated before.

In order to make the usage of our instance more comfortable, we additionally provide two functions which instruct the type system to use our particular instance for handling the FileSystem effect when running a particular program. We write one tagged and one untagged version:

runLocalFileSystem' :: (FileSystem' tag `Via` LocalFS) m a -> m a
runLocalFileSystem' = coerce

runLocalFileSystem :: (FileSystem `Via` LocalFS) m a -> m a
runLocalFileSystem = runLocalFileSystem' @G

We can also let effet generate the untagged version of runLocalFileSystem, so we don't have to write it by hand:

makeUntagged ['runLocalFileSystem']

Now let's provide similar definitions for our virtual file system. Instead of IO, we will use the Map effect that is shipped with effet in order to map file paths to their corresponding contents. For simplification, we will assume that the contents of non-existing files are empty strings:

newtype VirtualFS m a =
  VirtualFS { runVirtualFS :: m a }
    deriving (Applicative, Functor, Monad, MonadIO)
    deriving (MonadTrans, MonadTransControl) via IdentityT
    deriving (MonadBase b, MonadBaseControl b)

instance Map' tag FilePath String m => FileSystem' tag (VirtualFS m) where
  readFile' path = VirtualFS $ fromMaybe "" <$> lookup' @tag path
  writeFile' path txt = VirtualFS $ insert' @tag path txt

runVirtualFileSystem' :: (FileSystem' tag `Via` VirtualFS) m a -> m a
runVirtualFileSystem' = coerce

makeUntagged ['runVirtualFileSystem']

Using Effect Handlers

Now we have all our puzzle pieces together in order to run our program with different effect handlers. Let's recap the type of our program:

program :: (FileSystem m, Writer [Int] m) => FilePath -> String -> m ()

Let's see what happens if we use our runVirtualFileSystem function on that program after we feed it some file path and the content that should be appended to the file:

test :: (Map FilePath String m, Writer [Int] m) => m ()
test = runVirtualFileSystem $ program "/tmp/test.txt" "hello"

What happened? We handled the FileSystem effect using our virtual file system handler (i.e., we "plucked the constraint"), which gives us a new program as result that still needs its Map and Writer effects handled. We essentially reinterpreted one effect (FileSystem) in terms of another (Map) without touching all the other effects (Writer). In order to run the remaining two effects, we just need to import some of the pre-defined effect handlers for the Map and Writer effects:

import Control.Effect.Map.Lazy
import Control.Effect.Writer.Lazy

And here is the complete code for running our program either locally or virtually:

runLocalProgram :: MonadIO m => m ([Int], ())
runLocalProgram
  = runWriter
  . runLocalFileSystem
  $ program "/tmp/test.txt" "hello"

runVirtualProgram :: Monad m => m ([Int], ())
runVirtualProgram
  = runWriter
  . runMap
  . runVirtualFileSystem
  $ program "/tmp/test.txt" "hello"

As we can see by the types, no IO is involved in the virtual program, since we do not touch the actual file system.

We decided to handle our Map and Writer effects using their lazy implementations. We could, for example, easily switch the handlers to their strict counterparts, or even use some other Map-like implementation for our Map effect, like Redis.

Tagging, Retagging, Untagging

If we write a program with multiple FileSystem' effects, we can disambiguate them using the tags ...

fsProgram :: (FileSystem' "fs1" m, FileSystem' "fs2" m) => m ()
fsProgram = do
  writeFile' @"fs1" "/tmp/test.txt" "first content"
  writeFile' @"fs2" "/tmp/test.txt" "second content"

... and interpret them differently, one using the local and one using the virtual file system, for example:

runDifferently :: MonadIO m => m ()
runDifferently
  = runMap' @"fs1"
  . runVirtualFileSystem' @"fs1"
  . runLocalFileSystem' @"fs2"
  $ fsProgram

We could also use one tagged (FileSystem') and one untagged (FileSystem) effect to disambiguate them, of course.

We can also change the tags of our effects before interpretation using the generated tagFileSystem', retagFileSystem' and untagFileSystem' functions. We could, for example, merge the two effects into a single tag and interpret them uniformly:

runUniformly :: Monad m => m ()
runUniformly
  = runMap' @"fs1"
  . runVirtualFileSystem' @"fs1"
  . retagFileSystem' @"fs2" @"fs1"
  $ fsProgram

In the example above, we merge tag fs2 into tag fs1 and interpret them together using the virtual file system. We can achieve the same result by untagging both effects and then using the untagged versions of the interpretation functions:

runUntagged :: Monad m => m ()
runUntagged
  = runMap
  . runVirtualFileSystem
  . untagFileSystem' @"fs2"
  . untagFileSystem' @"fs1"
  $ fsProgram

Tagging, retagging and untagging are useful if we want to compose two functions which have the same effects, but we want to interpret them differently after composition. Note that there is a chance that two authors write two different libraries using the same effects, but do not know from each other ...

-- somewhere in library A
functionA :: FileSystem m => m ()
functionA = ...
-- somewhere in library B
functionB :: FileSystem m => m ()
functionB = ...

... and we want to compose these functions, but interpret the effects differently after composition:

-- oops, the effects were merged!
ourProgram :: FileSystem m => m ()
ourProgram = functionA >> functionB

We then need to introduce a tag for at least one of the two functions ...

-- now the effects are separated
ourProgram :: (FileSystem m, FileSystem' "b" m) => m ()
ourProgram = functionA >> tagFileSystem' @"b" functionB

... which again allows us to interpret the effects independently as described above.

Limitations and Remarks

  • TemplateHaskell-based code generation can yield code that does not compile if you go crazy with m-based parameters in higher-order effect methods (where m is the monad type parameter of the effect type class). In such cases, one has to write the necessary type class instances by hand. They are explained in the documentation of the module Control.Effect.Machinery.TH.
  • The performance should be mtl-like, but this has not been verified yet.
Note that the project description data, including the texts, logos, images, and/or trademarks, for each open source project belongs to its rightful owner. If you wish to add or remove any projects, please contact us at [email protected].