Obtaining Instances: The Instances Facade

Ask one class for every type class instance, in one shape: Instances.monad(maybe()).

Each type class in Higher-Kinded-J has a standard instance for each type, and org.higherkindedj.hkt.instances.Instances is where you ask for it. Every lookup has the same shape, Instances.x(...), so typing Instances. in an IDE lists every capability on offer.

What You'll Learn

  • Ask Instances for a Functor, Applicative, Monad or MonadError with a token from Witnesses
  • Pass the dependency an instance needs, such as a Semigroup, a Monoid or an outer Monad
  • Tell the lookups the compiler checks from the three that can fail at run time
  • Name the error type a MonadError really has

See Example Code

InstancesFacadeBookTest.java holds each claim this page makes, including that its tables match every token and every method.


Zero-argument lookups

For a type whose instance needs nothing, pass its token: a method in Witnesses, such as maybe(), that names the type's witness.

import static org.higherkindedj.hkt.instances.Witnesses.maybe;

import org.higherkindedj.hkt.Applicative;
import org.higherkindedj.hkt.Functor;
import org.higherkindedj.hkt.Monad;
import org.higherkindedj.hkt.instances.Instances;
import org.higherkindedj.hkt.maybe.MaybeKind;

Monad<MaybeKind.Witness>       monad       = Instances.monad(maybe());
Applicative<MaybeKind.Witness> applicative = Instances.applicative(maybe());
Functor<MaybeKind.Witness>     functor     = Instances.functor(maybe());

The instance implements the whole Functor, Applicative, Monad chain in one object, so all three calls return that same object.

Three more lookups give a richer capability, for the types whose instance has it. MonadError adds raising and recovering from an error; MonadZero adds an empty value and filter; Alternative adds an empty value and orElse. For Maybe and Optional, orElse falls back to a second value when the first is empty; for List, Stream and VStream, it joins the two.

MonadError<MaybeKind.Witness, Unit> me  = Instances.monadError(maybe());
MonadZero<ListKind.Witness>         mz  = Instances.monadZero(list());
Alternative<OptionalKind.Witness>   alt = Instances.alternative(optional());
LookupTypeError type
monadErrorMaybe, OptionalUnit
monadErrorTry, CompletableFuture, VTaskThrowable
monadErrorEither<L, _>L
monadZeroMaybe, Optional, List, Stream
alternativeMaybe, Optional, List, Stream, VStream

These three lookups compile for any token

The compiler cannot check that a type has the capability you ask for. A type without it, as in Instances.monadZero(either()), throws ClassCastException when the call runs. A wrong error type compiles too: MonadError<MaybeKind.Witness, String> throws ClassCastException once you raise a String. Take E from the table.

Type parameters come from your variable

Each token has a generic type, Of<F>, rather than a Class. Java has no generic class literals, so a Class would lose Either's left type or Reader's environment. The four tokens for types with such a parameter, either(), reader(), context() and state(), are generic methods, so the parameter comes from the variable you assign to:

// OrderError flows from the assignment target
Monad<EitherKind.Witness<OrderError>> m = Instances.monad(either());

Available tokens

FamilyTokens
No type parametermaybe(), io(), list(), nonEmptyList(), optional(), try_(), vtask(), vstream(), lazy(), stream(), completableFuture(), trampoline(), id()
A type parametereither(), reader(), context(), state()

try_() ends in an underscore because try is a reserved word in Java.


Argument-carrying instances

Validated, EitherOrBoth, Writer and the monad transformers each need a dependency: a Semigroup, which combines two values, a Monoid, which is a Semigroup with an empty value, or an outer Monad. None has a sensible default, so the facade asks for it in the method's parameters, where the compiler checks it and the IDE shows it:

MonadError<ValidatedKind.Witness<List<String>>, List<String>> v = Instances.validated(Semigroups.list());
Monad<EitherOrBothKind.Witness<List<String>>> eob = Instances.eitherOrBoth(Semigroups.list());
Monad<WriterKind.Witness<String>> w = Instances.writer(Monoids.string());

MonadError<EitherTKind.Witness<OptionalKind.Witness, String>, String> et =
    Instances.eitherT(Instances.monad(optional()));
MonadError<MaybeTKind.Witness<OptionalKind.Witness>, Unit> mt =
    Instances.maybeT(Instances.monad(optional()));
MonadError<OptionalTKind.Witness<MaybeKind.Witness>, Unit> ot =
    Instances.optionalT(Instances.monad(maybe()));
Monad<ReaderTKind.Witness<OptionalKind.Witness, String>> rt =
    Instances.readerT(Instances.monad(optional()));
Monad<StateTKind.Witness<Integer, OptionalKind.Witness>> st =
    Instances.stateT(Instances.monad(optional()));
MonadWriter<WriterTKind.Witness<OptionalKind.Witness, String>, String> wt =
    Instances.writerT(Instances.monad(optional()), Monoids.string());

A dependency in the signature cannot be missed: Instances.validated() with no Semigroup does not compile.

You can ship now

You can now get any built-in instance in one shape: a token for a type that needs nothing, an argument for one that needs a dependency. The rest of this page is for what the compiler checks, what the facade covers, and the accessors behind it.

Checkpoint: two lookups on IO

Which of these throws, and when: Instances.monad(io()) or Instances.alternative(io())?

Answer and why

Instances.alternative(io()) throws ClassCastException when the call runs, and Instances.monad(io()) succeeds. The compiler cannot check that a type has the capability you ask for, and IO's instance has no Alternative.

Where this lives: Zero-argument lookups.

Checkpoint: a token for Validated

maybe() is a token for Instances.monad(maybe()). Is there a validated() token, for Instances.monad(validated())?

var monad = Instances.monad(validated());

Answer and why

No: javac reports "cannot find symbol". Validated needs a Semigroup to join its errors, so it has no token. The facade asks for the Semigroup in the method's parameters instead: Instances.validated(Semigroups.list()).

Where this lives: Argument-carrying instances.


The fine print

What the compiler checks

Instances is a class of static methods, with no registry, no ServiceLoader and nothing to configure. A token lookup returns the instance its token carries, and a lookup that takes an argument builds a new instance on each call.

  • monad, applicative and functor are checked. Every token carries a Monad, so these three always succeed.
  • monadError, monadZero and alternative cast, and the compiler cannot check the cast. The warning in Zero-argument lookups shows what fails, and when.

The accessors behind the facade

Each class behind the facade has an accessor of its own, in one of four shapes. Instances is the one shape to use in new code:

Semigroup<String>            semi  = Semigroups.string();
Monad<OptionalKind.Witness>  outer = OptionalMonad.INSTANCE;

Monad<MaybeKind.Witness>             m1 = MaybeMonad.INSTANCE;            // a static field
Monad<EitherKind.Witness<String>>    m2 = EitherMonad.instance();         // a generic static method
MonadError<ValidatedKind.Witness<String>, String> v =
    ValidatedMonad.instance(semi);                                        // a factory with an argument
MonadError<EitherTKind.Witness<OptionalKind.Witness, String>, String> et =
    new EitherTMonad<>(outer);                                            // a constructor with an argument

Scope

Type classThrough Instances
Functor, Applicative, MonadYes, for every token
MonadError, MonadZero, AlternativeYes, for the types in the table in Zero-argument lookups
The instances that need an argument: validated, eitherOrBoth, writer, eitherT, maybeT, optionalT, readerT, stateT, writerTYes
MonadWriterOnly through writerT
MonadReader, MonadStateNot offered: ReaderTMonadReader and StateTMonadState are separate classes
Traverse, Selective, FoldableNot offered: each is a separate instance class, such as MaybeTraverse.INSTANCE
Free, FreeApplicativeNot offered: use new FreeMonad<>() or FreeApApplicative.instance()
ConstNot offered: it has an Applicative and a Bifunctor, and a token carries a Monad
Semigroup, MonoidNot offered: they are keyed by a value type, not a witness; use Semigroups and Monoids
Bifunctor, ProfunctorNot offered: they take a binary witness

See Also


Previous: Working with Kind Next: Functor