Identity Monad (Id)
The Identity Monad, often referred to as Id
, is the simplest possible monad. It represents a computation that doesn't add any additional context or effect beyond simply holding a value. It's a direct wrapper around a value.
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 specialized to their simpler, non-transformed monad counterparts by usingId
as the underlying monad. For example:StateT<S, Id.Witness, A>
is conceptually equivalent toState<S, A>
.MaybeT<Id.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>
,Id
can 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 anId
instance holdingvalue
.idInstance.value()
retrieves the value from theId
instance.
Key Classes and Concepts
Id<A>
: The data type itself. It's a final class that wraps a value of typeA
. It implementsKind<Id.Witness, A>
.Id.Witness
: A static nested class (or interface) used as the first type parameter toKind
(i.e.,F
inKind<F, A>
) to represent theId
type constructor at the type level. This is part of the HKT emulation pattern.IdKindHelper
: A utility class providing static helper methods:narrow(Kind<Id.Witness, A> kind)
: Safely casts aKind
back to a concreteId<A>
.widen(Id<A> id)
: widens anId<A>
toKind<Id.Witness, A>
. (Often an identity cast sinceId
implementsKind
).narrows(Kind<Id.Witness, A> kind)
: A convenience to narrow and then get the value.
IdentityMonad
: The singleton class that implementsMonad<Id.Witness>
, providing the monadic operations forId
.
Using Id
and IdentityMonad
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 IdentityMonad
provides the standard monadic operations.
public void monadExample(){
IdentityMonad idMonad = IdentityMonad.instance();
// 1. 'of' (lifting a value)
Kind<Id.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<Id.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<Id.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<Id.Witness, Function<Integer, String>> kindFunction = idMonad.of(i -> "Applied: " + i);
Kind<Id.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, Id.Witness, A>
.
Let's illustrate how you might define a State
monad type alias or use StateT
with IdentityMonad
:
public void transformerExample(){
// Conceptually, State<S, A> is StateT<S, Id.Witness, A>
// We can create a StateTMonad instance using IdentityMonad as the underlying monad.
StateTMonad<Integer, Id.Witness> stateMonadOverId =
StateTMonad.instance(IdentityMonad.instance());
// Example: A "State" computation that increments the state and returns the old state
Function<Integer, Kind<Id.Witness, StateTuple<Integer, Integer>>> runStateFn =
currentState -> Id.of(StateTuple.of(currentState + 1, currentState));
// Create the StateT (acting as State)
Kind<StateTKind.Witness<Integer, Id.Witness>, Integer> incrementAndGet =
StateTKindHelper.stateT(runStateFn, IdentityMonad.instance());
// Run it
Integer initialState = 10;
Kind<Id.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).