Skip to content

Law testing + Traits update + RWST

Pre-release
Pre-release
Compare
Choose a tag to compare
@louthy louthy released this 16 Oct 10:31
· 128 commits to main since this release

Laws testing

Functors, applicatives, and monads all have laws that make them what they are. Some think that Map just means the same as Map in mathematics, when in fact functors are more constrained and are structure-preserving.

The full set of laws are:

Functors

  • Identity law
  • Composition law
  • Structure preservation law

Applicatives

  • Identity law
  • Composition law
  • Homomorphism law
  • Interchange law
  • Applicative-functor law

Monads

  • Left-identity law
  • Right-identity law
  • Associativity law

When you write your own functors/applicatives/monads you are expected to honour these laws. In reality it's pretty hard to go wrong if you just follow the type-signatures and implement the traits in the most obvious way, but still, it is possible to make a mistake and some of the guarantees of the traits start to fail.

assert

The type-system isn't able to enforce many of the laws above, so we need to do it ourselves. I have now made that process much easier. If you implement a monadic type (using the new traits system) then can simply call:

MonadLaw<M>.assert();

Where M is your monad trait implementation type.

For example, this tests that Option complies with all of the laws listed above.

MonadLaw<Option>.assert();

If your type isn't a monad, but is an applicative, then you can call:

ApplicativeLaw<M>.assert();

And if your type isn't an applicative, but is a functor, then you can call:

var mx = M.Pure(123);
FunctorLaw<M>.assert(mx);

Functors don't know how to instantiate new functors (unlike applicatives and monads), so you must provide an instance to the assert function.

Note that, if your type is a monad and you call MonadLaw<M>.assert, you do not need to call ApplicativeLaw<M>.assert or FunctorLaw<M>.assert. Those will be tested automatically.

validate

The assert functions listed above are perfect for unit-tests, but you can call validate instead. It will return a Validation<Error, Unit> which will collect a set of failures for any failing laws.

var result = MonadLaw<Option>.validate();

Equality

The functions that test that the laws hold need to be able to test equality of functor/monad/applicative values. Unfortunately, not all functors/applicatives/monads support equality. Types like Reader, for example, are computations (not values), and so must be evaluated to extract a concrete value. The generic traits don't know how to evaluate them to extract the values.

And so, for types that have no valid Equals implementation, you must provide an equality function to assert and validate.

Here's an example for Eff<int>:

  bool eq(K<Eff, int> vx, K<Eff, int> vy) => 
      vx.Run().Equals(vy.Run());
  
  MonadLaw<Eff>.assert(eq);

It's pretty simple, it just runs the effect and compares the result.

Examples

You can look at the unit-tests for all of the functor/applicative/monad types in language-ext:

Future

  • More laws tested for more traits!
  • Potentially add these assertions to a Roslyn analyzer (if anyone wants to try, please do!)

Removal of Alternative and SemiAlternative

I have removed Alternative and SemiAlternative traits. I really disliked the name SemiAlternative (which was a combination of SemigroupK and Applicative. I was OK with Alternative (MonoidK and Applicative) but it doesn't make sense without its semigroup partner. So, for now, we will only have SemigroupK and MonoidK (semigroup and monoid that work for K<F, A> rather than A).

I'm still refining the types and am not 100% happy with this, but am short of ideas for better names or approaches. Feel free to let me know what you think.

Pure to pure

The computation types: Reader, ReaderT, State, StateT, Writer, and WriterT have all had their module Pure function renamed to pure -- as it's not strictly a constructor, it simply lifts a pure value into those computations.

RWST

Reader/Write/State monad-transformer. This is still WIP but it should be usable. It just doesn't have all the bells and whistles yet.