A minimalistic effect system: parameters-fx

Preamble

I’ve been trying for a long time to fix certain problems with implicit parameters (see Why not implicit parameters? - #15 by lortabac) without succeeding completely.

Even though imperfect, I have a library that addresses at least some of the issues: GitHub - lortabac/parameters: Implicit parameters with Reader semantics

This library provides a Reader-like interface:

  • HasParam is a constraint that shows that a parameter is in scope
  • runParam introduces a parameter
  • ask retrieves the value of a parameter
  • local overrides the parameter locally (support is partial due to unsolved issues)

The effect system

While working on this library I realized that once you have implicit parameters and withDict you can create a very simple yet powerful effect system. I called this effect system parameters-fx.

parameters-fx provides an IO wrapper called Fx. Fx is really just a newtype over IO except that the constructor is not exported, so if you want to run IO you need to use one of the smart constructors.

The types of the smart constructors ensure that each Fx action must require a constraint.

There are three ways to construct an Fx action:

  • If the action depends on an implicit parameter:
    fx :: forall p a r. (HasParam p a) => ((HasParam p a) => IO r) -> Fx r
  • If the action doesn’t depend on an implicit parameter:
    fxNoParam :: forall eff r. (HasEffect eff) => ((HasEffect eff) => IO r) -> Fx r
  • If you want to use a “catch-all” constraint (support for this method needs to be enabled explicitly):
    embedIO :: (HasIO) => IO a -> Fx a

That’s it. The whole effect system is 23 lines of code: parameters/parameters-fx/src/Param/Fx.hs at master · lortabac/parameters · GitHub

State and Error effects are provided for convenience. I think other effects should be implemented as separate libraries.

To be honest I don’t have a big interest in effect systems. But I discovered this trick and thought it may be useful. If there is enough interest I may decide to keep the project alive otherwise it will have been a fun experiment.

Please tell me what you think.

6 Likes

For running Fx I see runFx :: Fx r -> IO r. In cases where all effects have been handled, so no effect is externally visible, we’d really want something of type Fx r -> r. Is this possible with parameters-fx?

No, this is not possible.

The point of Fx is to provide more granularity to IO. And the only place where IO is discharged is the main.

1 Like

does that mean effects like Reader r or State s are out of scope for parameter-fx? if you take the precautions to prevent IO or IO based effects from being executed unless you have a hasIO constraint 99% chance you can just do what bytestring does and use a variant of unsafePerformIO. effectful uses a MVar for its shared state effect and unsafeDupablePerformIO for its runPureEff function.

The lack of a runPureFx is not due to a technical limitation. It’s just that I don’t see a compelling use case.

If you need Reader o ReaderT you can use implicit parameters (see the parameters library in the same repo).

If you need local mutability you can use the State transformer or ST. An effect system wouldn’t add much in this case.

If you need StateT you are most likely already in IO territory.

Yes, there may be some particular combinations of effects that would really require runFxPure but I can’t think of one that is common enough to justify an unsafePerformIO.