Probably one of the least useful monads. An article for object-oriented developers.

This article is an instalment in an article series about monads.

The Identity functor is another example of a functor that also forms a monad. Contrary to useful monads like list, Maybe, and Either, the Identity monad isn't particularly useful. In Haskell it serves a few niche positions, for example when it comes to monad transformers. It's possible to define abstractions at a higher level in Haskell than it is in many other languages. Some of those abstractions involve monads. The Identity monad can plug into these abstractions as a 'do-nothing' monad, a bit like the Null Object pattern can supply a 'do-nothing' implementation of an interface.

Other languages with which I'm proficient (C#, F#) don't enable that kind of abstraction, and I've never felt the need to actually use the Identity monad in those languages. Still, the Identity monad exists mathematically and conceptually, and it can be useful to know of that existence. This implies, like the Null Object pattern, that some abstractions can be simplified by 'plugging in' the Identity monad. The alternative would have had to involve some hand-waving about special cases.

The code in this article continues the implementation shown in the article about the Identity functor.

SelectMany #

A monad must define either a bind or join function. In C#, monadic bind is called SelectMany. For Identity<T> the implementation is trivial:

public Identity<TResult> SelectMany<TResult>(Func<T, Identity<TResult>> selector)
{
    return selector(Item);
}

This is an instance method on Identity<T>, which means that the Item property has the type T. Since the selector return a new Identity<TResult>, the method can simply return that value.

Query syntax #

As usual, you can implement C# query syntax with a boilerplate overload of SelectMany:

public Identity<TResult> SelectMany<UTResult>(
    Func<T, Identity<U>> k,
    Func<T, U, TResult> s)
{
    return SelectMany(x => k(x).Select(y => s(x, y)));
}

The implementation is the same as in the previous articles in this article series. If it looks a little different than, say, the implementation for the Maybe monad, it's only because this SelectMany overload is an instance method, whereas the Maybe implementation is an extension method.

Query syntax enables syntactic sugar like this:

Identity<inti = from x in new Identity<int>(39)
                  from y in new Identity<string>("foo")
                  select x + y.Length;

The C# compiler infers that x is an int and y is a string.

Flatten #

As the monad introduction also points out, if you have monadic bind (SelectMany) you can always implement a function to flatten a nested functor. This function is sometimes called join and sometimes (perhaps more intuitively) flatten. Here I've chosen to call it Flatten:

public static Identity<T> Flatten<T>(this Identity<Identity<T>> source)
{
    return source.SelectMany(x => x);
}

Even though the other methods shown so far have been instance methods, Flatten has to be an extension method, since the source is a nested Identity<Identity<T>>. There's no class of that specific type - only Identity<T>.

If there's one useful aspect of the Identity monad, it may be that it reveals the concept of a nested functor in all its triviality:

Identity<Identity<string>> nested =
    new Identity<Identity<string>>(new Identity<string>("bar"));
Identity<stringflattened = nested.Flatten();

Recall: A monad is a functor you can flatten.

Return #

Apart from monadic bind, a monad must also define a way to put a normal value into the monad. Conceptually, I call this function return (because that's the name that Haskell uses). You don't, however, have to define a static method called Return. What's of importance is that the capability exists. For Identity<T> in C# the idiomatic way would be to use a constructor. This constructor does double duty as monadic return:

public Identity(T item)
{
    Item = item;
}

In other words, return just wraps a value in the Identity<T> container.

Left identity #

We need to identify the return function in order to examine the monad laws. Let's see what they look like for the Identity monad, starting with the left identity law.

[Property(QuietOnSuccess = true)]
public void IdentityHasLeftIdentity(Func<int, Identity<string>> hint a)
{
    Func<int, Identity<int>> @return = i => new Identity<int>(i);
    Assert.Equal(@return(a).SelectMany(h), h(a));
}

This example is a bit different compared to the previous examples, since it uses FsCheck 2.11.0 and xUnit.net 2.4.0. FScheck can generate arbitrary functions in addition to arbitrary values, so I've simply asked it to generate some arbitrary h functions.

Right identity #

In a similar manner, we can showcase the right identity law as a test.

[Property(QuietOnSuccess = true)]
public void IdentityHasRightIdentity(Func<string, Identity<int>> fstring a)
{
    Func<int, Identity<int>> @return = i => new Identity<int>(i);
    Identity<intm = f(a);
    Assert.Equal(m.SelectMany(@return), m);
}

As always, even a property-based test constitutes no proof that the law holds. I show it only to illustrate what the laws look like in 'real' code.

Associativity #

The last monad law is the associativity law that describes how (at least) three functions compose.

[Property(QuietOnSuccess = true)]
public void IdentityIsAssociative(
    Func<int, Identity<string>> f,
    Func<string, Identity<byte>> g,
    Func<byte, Identity<TimeSpan>> h,
    int a)
{
    Identity<stringm = f(a);
    Assert.Equal(m.SelectMany(g).SelectMany(h), m.SelectMany(x => g(x).SelectMany(h)));
}

This property once more relies on FsCheck's ability to generate arbitrary pure functions.

F# #

While the Identity monad is built into the Haskell standard library, there's no Identity monad in F#. While it can be occasionally useful in Haskell, Identity is as useless in F# as it is in C#. Again, that doesn't imply that you can't define it. You can:

type Identity<'a> = Identity of 'a
 
module Identity =
    // Identity<'a> -> 'a
    let get (Identity x) = x
    // ('a -> 'b) -> Identity<'a> -> Identity<'b>
    let map f (Identity x) = Identity (f x)
    // ('a -> Identity<'b>) -> Identity<'a> -> Identity<'b>
    let bind f (Identity x) : Identity<_> = f x

Here I've repeated the functor implementation from the article about the Identity functor, but now also included the bind function.

You can use such a module to support syntactic sugar in the form of computation expressions:

type IdentityBuilder() =
    member _.Bind(x, f) = Identity.bind f x
    member _.Return x = Identity x
    member _.ReturnFrom x = x
 
let identity = IdentityBuilder ()

This enables you to write code like this:

let i = identity {
    let! x = Identity "corge"
    let! y = Identity 47
    return y - x.Length }

Here, i is a value of the type Identity<int>.

Conclusion #

If the Identity monad is useful in languages like C# and F#, I have yet to encounter a use case. Even so, it exists. It is also, perhaps, the most naked illustration of what a functor and monad is. As such, I think it may be helpful to study.

Next: The Lazy monad.



Wish to comment?

You can add a comment to this post by sending me a pull request. Alternatively, you can discuss this post on Twitter or somewhere else with a permalink. Ping me with the link, and I may respond.

Published

Monday, 16 May 2022 05:49:00 UTC

Tags



"Our team wholeheartedly endorses Mark. His expert service provides tremendous value."
Hire me!
Published: Monday, 16 May 2022 05:49:00 UTC