freer-simple

repository·master·Indexed 42 days ago

https://github.com/lexi-lambda/freer-simple

An extensible effect system for Haskell that allows developers to track and handle side-effects at the type level. It provides built-in implementations for common monads such as Reader, Writer, State, and Error, and utilizes GADTs and Template Haskell via makeEffect to define and interpret custom effects.

Tokens
1.2K
Snippets
1
Records
2
Agent score
4%

What's inside freer-simple

  1. What is freer-simple and how does it work?

    master

    The freer-simple library provides an implementation of an extensible effect system for Haskell. It allows you to track effects (such as state mutation, file system interaction, random number generation, or environment access) at the type level.

    Key capabilities include:

    • An efficient effect system designed as a library.
    • Built-in implementations for common monads like Reader, Writer, State, and Error.
    • A combinator language for defining and composing custom effects.

    Users typically define their own effects using GADTs and then write interpreters to handle those effects, either by performing real-world side effects (e.g., using IO) or by simulating them purely (e.g., using State or Writer).

  2. Define and interpret custom effects in freer-simple

    master

    To use freer-simple, you follow a three-step pattern:

    1. Define the effect: Use a GADT to represent the operations of your effect.
    2. Generate combinators: Use Template Haskell (makeEffect) to automatically generate the functions needed to trigger the effect.
    3. Write an interpreter: Create a function that maps your effect to other effects (like IO or pure state) to define its behavior.

    Below is a complete example demonstrating a Console effect with both an effectful IO interpreter and a pure interpreter for testing.

    {-# LANGUAGE DataKinds #-}
    {-# LANGUAGE FlexibleContexts #-}
    {-# LANGUAGE GADTs #-}
    {-# LANGUAGE LambdaCase #-}
    {-# LANGUAGE TemplateHaskell #-}
    {-# LANGUAGE TypeOperators #-}
    
    import qualified Prelude
    import qualified System.Exit
    
    import Prelude hiding (putStrLn, getLine)
    
    import Control.Monad.Freer
    import Control.Monad.Freer.TH
    import Control.Monad.Freer.Error
    import Control.Monad.Freer.State
    import Control.Monad.Freer.Writer
    
    --------------------------------------------------------------------------------
                                   -- Effect Model --
    --------------------------------------------------------------------------------
    data Console r where
      PutStrLn    :: String -> Console ()
      GetLine     :: Console String
      ExitSuccess :: Console ()
    makeEffect ''Console
    
    --------------------------------------------------------------------------------
                              -- Effectful Interpreter --
    --------------------------------------------------------------------------------
    runConsole :: Eff '[Console, IO] a -> IO a
    runConsole = runM . interpretM (\case
      PutStrLn msg -> Prelude.putStrLn msg
      GetLine -> Prelude.getLine
      ExitSuccess -> System.Exit.exitSuccess)
    
    --------------------------------------------------------------------------------
                                 -- Pure Interpreter --
    --------------------------------------------------------------------------------
    runConsolePure :: [String] -> Eff '[Console] w -> [String]
    runConsolePure inputs req = snd . fst $
        run (runWriter (runState inputs (runError (reinterpret3 go req))))
      where
        go :: Console v -> Eff '[Error (), State [String], Writer [String]] v
        go (PutStrLn msg) = tell [msg]
        go GetLine = get >>= \case
          [] -> error "not enough lines"
          (x:xs) -> put xs >> pure x
        go ExitSuccess = throwError ()