Giter VIP home page Giter VIP logo

haskell-benign's Introduction

Benign

A library for benign effects in Haskell

The current state is a proof of concept, it is not meant to be used in actual projects nor do I guarantee any stability yet

Philosophy

A core tenet of Haskell programming is “pure core, imperative shell”. We write most of our programs in pure style, and a small outer layer in imperative shell. This is great for correctness, but it comes at a great cost for observability. Because logging, tracing, or measuring time are all considered effects, we can't log, trace or measure time in pure code.

This means that most of our code is traditionally unobservable. There are some built-in facilities in GHC, such as cost-centre profiling. But besides putting pressure on the compiler to build these tools, these facilities are out of the language, hence not programmable in anyway way.

The Benign library is an attempt to address this gap. A difficulty is laziness. With laziness, things have a beginning (when the thunk is being forced), but not a well-defined end. So what do we measure the time of? The solution of the Benign library is to be less lazy. We keep laziness for algorithms, but use strict (or at least stricter) types to assemble bigger steps. It's fine to log or trace in pure code, since we don't consider that these observations are part of the semantics of the program.

The Benign library provides facilities to create benign effects, including to use strict types to assemble these large steps.

The premise underlying all this, as well as the implementation of the library, is that logging or tracing is not very fast. So we don't want to log or trace in places where performance is really essential. This is why at the most inner level, where tight loops and algorithms live, laziness is not a problem: we are not going to log there, this would cost too much performance. At a more outer level, we can use strictness to make steps with a beginning and an end. It's ok if there is a cost in terms of conversion, this is not where we need to optimise too much. The library can, and does, prevent optimisation through its functions (note that cost-centre profiling also prevents optimisation through cost centres; it isn't surprising that we are having a similar problem). With the optimisation not working at that level, strictness is much less of a problem.

Backends

Backends are shipped as separate package (with the exception of backends which require no additional dependencies).

I've got to confess that it's a major pain in my delicately dignified bottom. The language server works rather poorly across packages; duplicating the Cabal files is somewhat annoying, what with the common stanzas being lost and the version numbers being easier to get wrong, I'll have to publish several packages every time I want to make a release. I'm probably forgetting other issues. But until Hackage supports multiple-library packages, it's the only solution that I have to avoid pulling, say, Katip, when I want to use Opentelemetry.

You are welcome to write benign effects directly in your package. If you wish to upstream one of my backends: I'll deprecate mine.

Build

Build as

$ stack build

The formatter is Ormolu 0.5.0.1. Format with

$ just format

You can use Nix (specifically nix-shell) to provide all the necessary dependencies (including a compatible Haskell Language Server).

For reasons unknown to me, Ormolu is not cached in the pinned version of Nix. I haven't had time to investigate, instead I'm using Cachix. For the nix-shell to load fast, install cachix and run (once per machine) prior to building the Nix shell.

$ cachix use aspiwack

Acknowledgement

Credits go to

  • Alexis King for helping me come up with the implementation strategy based on async and thread ids.
  • Thomas Bagrel for suggesting (in another context) the phrase “lexical state” to me. I don't know whether the phrase has been used before, a quick googling didn't turn up anything.

haskell-benign's People

Contributors

aspiwack avatar

Stargazers

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

Watchers

 avatar  avatar

haskell-benign's Issues

Check the Haddock across the repository

In particular

  • I don't export internal values
  • Add sections where it's useful
  • Check the rendering
  • Make sure that everything is at least minimally documented
  • Make sure that each of the backend packages explains its relation with the Benign package.

Plug memory leaks in `withAltering`

There is comment there, but the withAltering function can presumably leak memory (the local state for the newly created async would fail to be removed) when an asynchronous exception happens at the wrong time. This is not a functional requirement, but it's still bad.

Have tests

I don't have tests for this repository, yet. It's hard to test this repository. I think that we can have golden tests, at least for logging frameworks (of the form: evaluate an expression, check the output). I rather like tasty-golden, I guess I'm going to use it.

It's not going to be super easy for every backend, though. For instance: I don't think that I can reliably read the output of the eventlog from inside my application.

I don't really have a plan yet.

Test the ghc-events-analyze backend

I wrote a ghc-events-analyze backend in Benign.GhcEventsAnalyze. But I've got to confess to never testing it. It would be good if someone made a small program and tested that it worked.

We could contribute this program to the examples/ directory.

Consider introducing an `Evaluation` monad

The Evaluation (possible alternative name Strategy) monad would have a single primitive evaluate (the same as the IO monad. In fact, under the hood, it would possibly be the IO monad, though maybe there is a pure solution).

The idea would be that the Evaluation monad would support a (safe) run :: Evaluation a -> a function, contrary to the IO monad.

It may be less clunky to use than the Thunk/extractEval approach that I currently use. On the other hand, it may make it harder to deal with strict types.


Something that I would love to get out of this is to be able to unify somehow the classes Eval and EvalIO (the latter being introduced by #16 ). But the difficulty remains that EvalIO (IO a) must hold and Eval (IO a) absolutely mustn't. How can these be conciliated?

Do not use TVars

I've used TVars in my implementation because they're quite convenient. But the truth is, TVars under unsafePerformIO are tremendously dangerous (well, more precisely, atomically under unsafePerformIO is tremendously dangerous). Because it creates the possibility of nested atomically, and these are caught by the compilers and raise errors.

In a sense, this violates purity. So we should replace all this with MVars. I don't expect this to be particularly challenging.

Better TLDR in ghc-events-analyze

This is a companion to #9 .

I've put some very simple instruction at the top of the Benign.GhcEventsAnalyze module. But I haven't tested them, and they can probably be improved.

Add a primitive to create beginning-only effects

Effects of the sort of Debug.Trace are “beginning only” they are triggered when a thunk is forced, and need no end. Such effects wouldn't need an evaluation strategy, and would be a little easier/more lightweight to use. Logging is probably usually of this kind. The best way to check is to wrap a logging framework and see if the need arises.

Auto-derive SeqIsEval for Generic type

Idea suggested by @Profpatsch on twitter

We could add an instance instance (Generic a, …) => SeqIsEval a. This instance would check that every field is strict (is it possible in the generic framework? I expect it is. Though I imagine that it's a little bit of work).

This instance is overlapping, but there is no risk in overlapping instances here: SeqIsEval has trivial evidence, so is automatically coherent. On the other hand, we need to be a little careful with error messages, because the instance will always trigger. It needs to point the user in the right direction if it eventually fails.

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.