Effects utilities

Contains utility functions extending the functionality of the eff package.


module Util.Eff

import Control.Eff
import Data.Subset
import Text.ANSI

import System
import System.File

%default total

Logging

Log Levels

Basic enumeration describing log levels, we define some (hidden) utility functions for working with these.

The Other n log level is restricted to n greater than 4, to prevent ambiguity between custom log levels and predefined log levels.


public export
data Level : Type where
  Err : Level
  Warn : Level
  Info : Level
  Debug : Level
  Trace : Level
  Other : (n : Nat) -> {auto _ : n `GT` 4} -> Level

Convert a Level into a colorized tag


export
levelToTag : Level -> String
levelToTag Err =
    show . bolden . show . colored BrightRed $ "[Error]"
levelToTag Warn =
    show . bolden . show . colored BrightYellow $ "[Warning]"
levelToTag Info =
    show . bolden . show . colored Green $ "[Info]"
levelToTag Debug =
    show . bolden . show . colored Magenta $ "[Debug]"
levelToTag Trace =
    show . bolden . show . colored Cyan $ "[Trace]"
levelToTag (Other k) =
    show . bolden . show . colored BrightWhite $ "[\{show k}]"

Logger effect

This is a basic data structure that captures a lazy log message (so we don't have to pay any of the costs associated with generating the log message when it is filtered)


public export
data Logger : Type -> Type where
  Log : (level : Level) -> (msg : Lazy String) -> Logger ()

We'll also provide some basic accessors, and an ignore function useful for writing handlers.


export  
(.level) : Logger t -> Level
(.level) (Log level msg) = level

export
(.msg) : Logger t -> Lazy String
(.msg) (Log level msg) = msg

export
ignore : Logger t -> t
ignore (Log level msg) = ()

Handler

Because we know that we will only be using logger in an IO context, we aren't currently going to provide a runLogger or the like, instead we'll define a function, suitable for use as a runEff handler, that filters log messages and prints them to stderr over IO.

In the event a log message is filtered out, it's inner message is never inspected, avoiding evaluation.


export
handleLoggerIO : 
  (max_level : Level) -> Logger t -> IO t
handleLoggerIO max_level x = 
  if x.level <= max_level 
    then do
      _ <- fPutStrLn stderr "\{levelToTag x.level}: \{x.msg}"
      pure . ignore $ x
    else pure . ignore $ x

Provide a family of effectful actions that emit log messages at the different log levels.


export
error : Has Logger fs => Lazy String -> Eff fs ()
error x = send $ Log Err x

export
warn : Has Logger fs => Lazy String -> Eff fs ()
warn x = send $ Log Warn x

export
info : Has Logger fs => Lazy String -> Eff fs ()
info x = send $ Log Info x

export
debug : Has Logger fs => Lazy String -> Eff fs ()
debug x = send $ Log Debug x

export
trace : Has Logger fs => Lazy String -> Eff fs ()
trace x = send $ Log Trace x

export
logAt : Has Logger fs => Level -> Lazy String -> Eff fs ()
logAt level x = send $ Log level x

Choose

Lifts any foldable data structure into the Choose effect, enabling the style of nondeterministic programming from the list monad


export
oneOf : Has (Choose) fs => Foldable f =>
    f a -> Eff fs a
oneOf x = foldl oneOf' empty x
  where
  oneOf' : Eff fs a -> a -> Eff fs a
  oneOf' acc val = pure val `alt` acc

Lifts any foldable of effectful computations into the Choose effect much the same as oneOf


export
oneOfM : Has (Choose) fs => Foldable f =>
    f (Eff fs a) -> Eff fs a
oneOfM x = foldl alt empty x

Subset

Reorder the first two elements of the first argument of a subset


public export
subsetReorderLeft : Subset (x :: g :: xs) ys -> Subset (g :: x :: xs) ys
subsetReorderLeft (e :: (e' :: subset)) = e' :: e :: subset

Add the same element to both arguments of a subset


public export
subsetConsBoth : {0 xs, ys : List a} -> {0 g : a} -> Subset xs ys 
  -> Subset (g :: xs) (g :: ys)
subsetConsBoth {xs = []} {ys = ys} [] = [Z]
subsetConsBoth {xs = (x :: xs)} {ys} (e :: subset) = 
  let rest = S e :: subsetConsBoth subset {g}
  in subsetReorderLeft rest

A list is always a subset of itself


public export
subsetReflexive : {xs : List a} -> Subset xs xs
subsetReflexive {xs = []} = []
subsetReflexive {xs = (x :: xs)} = 
  let rest = subsetReflexive {xs} 
  in subsetConsBoth rest

If xs is a subset of ys, then it is also a subset of ys + g


public export
subsetConsRight : {0 g: _} -> {0 xs, ys: _} -> Subset xs ys -> Subset xs (g :: ys)
subsetConsRight {xs = []} {ys = ys} [] = []
subsetConsRight {xs = (x :: xs)} {ys} (e :: subset) = 
  S e :: subsetConsRight subset {g}

xs is always a subset of xs + g


public export
trivialSubset : {0 g : a} -> {xs : List a} -> Subset xs (g :: xs)
trivialSubset = subsetConsRight subsetReflexive

fs - f is always a subset of f


public export
minusLemma : {fs : List a} -> {0 x : a} -> {auto prf : Has x fs} 
  -> Subset (fs - x) fs
minusLemma {fs = (_ :: vs)} {prf = Z} = trivialSubset
minusLemma {fs = (w :: vs)} {prf = (S y)} = 
  let rest = minusLemma {fs = vs} {prf = y}
  in subsetConsBoth rest