Identity Monad (Id)
While it might seem trivial on its own, the Identity Monad plays a crucial role in a higher-kinded type library for several reasons:
-
Base Case for Monad Transformers: Many monad transformers (like
StateT,ReaderT,MaybeT, etc.) can be specialised to their simpler, non-transformed monad counterparts by usingIdas the underlying monad. For example:StateT<S, IdKind.Witness, A>is conceptually equivalent toState<S, A>.MaybeT<IdKind.Witness, A>is conceptually equivalent toMaybe<A>. This allows for a unified way to define transformers and derive base monads.
-
Generic Programming: When writing functions that are generic over any
Monad<F>,Idcan serve as the "no-effect" monad, allowing you to use these generic functions with pure values without introducing unnecessary complexity. -
Understanding Monads: It provides a clear example of the monadic structure (
of,flatMap,map) without any distracting side effects or additional computational context.
What is Id?
An Id<A> is simply a container that holds a value of type A.
Id.of(value)creates anIdinstance holdingvalue.idInstance.value()retrieves the value from theIdinstance.
Key Classes and Concepts
Id<A>: The data type itself. It's a record that wraps a value of typeA. It implementsIdKind<A>, which extendsKind<IdKind.Witness, A>.IdKind<A>: The Kind interface marker for theIdtype. It extendsKind<IdKind.Witness, A>, following the standard Higher-Kinded-J pattern used by other types likeTrampolineKindandFreeKind.IdKind.Witness: A static nested class withinIdKindused as the phantom type marker (theFinKind<F, A>) to represent theIdtype constructor at the type level. This is part of the HKT emulation pattern.IdKindHelper: A utility class providing static helper methods:narrow(Kind<IdKind.Witness, A> kind): Safely casts aKindback to a concreteId<A>.widen(Id<A> id): widens anId<A>toKind<IdKind.Witness, A>. (Often an identity cast sinceIdimplementsKind).narrows(Kind<IdKind.Witness, A> kind): A convenience to narrow and then get the value.
IdMonad: The singleton class that implementsMonad<IdKind.Witness>, providing the monadic operations forId.
Using Id and IdMonad
public void createExample(){
// Direct creation
Id<String> idString = Id.of("Hello, Identity!");
Id<Integer> idInt = Id.of(123);
Id<String> idNull = Id.of(null); // Id can wrap null
// Accessing the value
String value = idString.value(); // "Hello, Identity!"
Integer intValue = idInt.value(); // 123
String nullValue = idNull.value(); // null
}
The IdMonad provides the standard monadic operations.
public void monadExample(){
IdMonad idMonad = IdMonad.instance();
// 1. 'of' (lifting a value)
Kind<IdKind.Witness, Integer> kindInt = idMonad.of(42);
Id<Integer> idFromOf = ID.narrow(kindInt);
System.out.println("From of: " + idFromOf.value()); // Output: From of: 42
// 2. 'map' (applying a function to the wrapped value)
Kind<IdKind.Witness, String> kindStringMapped = idMonad.map(
i -> "Value is " + i,
kindInt
);
Id<String> idMapped = ID.narrow(kindStringMapped);
System.out.println("Mapped: " + idMapped.value()); // Output: Mapped: Value is 42
// 3. 'flatMap' (applying a function that returns an Id)
Kind<IdKind.Witness, String> kindStringFlatMapped = idMonad.flatMap(
i -> Id.of("FlatMapped: " + (i * 2)), // Function returns Id<String>
kindInt
);
Id<String> idFlatMapped = ID.narrow(kindStringFlatMapped);
System.out.println("FlatMapped: " + idFlatMapped.value()); // Output: FlatMapped: 84
// flatMap can also be called directly on Id if the function returns Id
Id<String> directFlatMap = idFromOf.flatMap(i -> Id.of("Direct FlatMap: " + i));
System.out.println(directFlatMap.value()); // Output: Direct FlatMap: 42
// 4. 'ap' (applicative apply)
Kind<IdKind.Witness, Function<Integer, String>> kindFunction = idMonad.of(i -> "Applied: " + i);
Kind<IdKind.Witness, String> kindApplied = idMonad.ap(kindFunction, kindInt);
Id<String> idApplied = ID.narrow(kindApplied);
System.out.println("Applied: " + idApplied.value()); // Output: Applied: 42
}
As mentioned in the StateT Monad Transformer documentation, State<S,A> can be thought of as StateT<S, IdKind.Witness, A>.
Let's illustrate how you might define a State monad type alias or use StateT with IdMonad:
public void transformerExample(){
// Conceptually, State<S, A> is StateT<S, IdKind.Witness, A>
// We can create a StateTMonad instance using IdMonad as the underlying monad.
StateTMonad<Integer, IdKind.Witness> stateMonadOverId =
StateTMonad.instance(IdMonad.instance());
// Example: A "State" computation that increments the state and returns the old state
Function<Integer, Kind<IdKind.Witness, StateTuple<Integer, Integer>>> runStateFn =
currentState -> Id.of(StateTuple.of(currentState + 1, currentState));
// Create the StateT (acting as State)
Kind<StateTKind.Witness<Integer, IdKind.Witness>, Integer> incrementAndGet =
StateTKindHelper.stateT(runStateFn, IdMonad.instance());
// Run it
Integer initialState = 10;
Kind<IdKind.Witness, StateTuple<Integer, Integer>> resultIdTuple =
StateTKindHelper.runStateT(incrementAndGet, initialState);
// Unwrap the Id and then the StateTuple
Id<StateTuple<Integer, Integer>> idTuple = ID.narrow(resultIdTuple);
StateTuple<Integer, Integer> tuple = idTuple.value();
System.out.println("Initial State: " + initialState); // Output: Initial State: 10
System.out.println("Returned Value (Old State): " + tuple.value()); // Output: Returned Value (Old State): 10
System.out.println("Final State: " + tuple.state()); // Output: Final State: 11
}
This example shows that StateT with Id behaves just like a standard State monad, where the "effect" of the underlying monad is simply identity (no additional effect).