The Identity functor by Mark Seemann
The Identity functor is a data container that doesn't do anything. It's a functor nonetheless. An article for object-oriented programmers.
This article is an instalment in an article series about functors. In previous articles, you've learned about useful functors such as Maybe. In this article, you'll learn about a (practically) useless functor called Identity. You can skip this article if you want.
If Identity is useless, then why bother?
The inutility of Identity doesn't mean that it doesn't exist. The Identity functor exists, whether it's useful or not. You can ignore it, but it still exists. In C# or F# I've never had any use for it (although I've described it before), while it turns out to be occasionally useful in Haskell, where it's built-in. The value of Identity is language-dependent.
You may still derive value from this article. My motivation for writing it is to add another example to the set of functor examples presented in this article series. By presenting a varied selection of functors, it's my hope that you, from examples, gain insight.
Identity objects #
You can implement Identity as a C# class:
public sealed class Identity<T> { public Identity(T item) { Item = item; } public T Item { get; } public Identity<TResult> Select<TResult>(Func<T, TResult> selector) { return new Identity<TResult>(selector(Item)); } public override bool Equals(object obj) { if (!(obj is Identity<T> other)) return false; return Equals(Item, other.Item); } public override int GetHashCode() { return Item?.GetHashCode() ?? 0; } }
This class is just a wrapper around any object of the generic type T
. The value could even be null
if T
is a reference type.
Since Identity<T>
exposes the Select
instance method, you can translate wrapped values, as this C# Interactive example demonstrates:
> new Identity<string>("foo").Select(s => s.Length) Identity<int> { Item=3 }
You can also, if you prefer, use query syntax:
> from i in new Identity<int>(2) select 'f' + new String('o', i) Identity<string> { Item="foo" }
You can also unwrap the value:
> Identity<string> x = new Identity<string>("bar"); > x.Item "bar"
Admittedly, this isn't the most exciting or useful class you could ever write. It is, however, a functor.
First functor law #
The Select
method obeys the first functor law. As usual in this article series, actually proving that this is the case belongs in the realm of computer science. I would guess, however, that in this particular case, the proof would be manageable. Instead of proving this, though, you can demonstrate that the law holds using property-based testing. The following example shows a single property written with FsCheck 2.11.0 and xUnit.net 2.4.0.
[Property(QuietOnSuccess = true)] public void IdentityObeysFirstFunctorLaw(int i) { var left = new Identity<int>(i); var right = new Identity<int>(i).Select(x => x); Assert.Equal(left, right); }
Even though you may feel that a property-based test gives you more confidence than a few hard-coded examples, such a test is nothing but a demonstration of the first functor law. It's no proof, and it only demonstrates that the law holds for Identity<int>
, not that it holds for Identity<string>
, Identity<Customer>
, etcetera.
Second functor law #
As is the case with the first functor law, you can also use a property to demonstrate that the second functor law holds:
[Property(QuietOnSuccess = true)] public void IdentityObeysSecondFunctorLaw( Func<string, byte> f, Func<int, string> g, int i) { var left = new Identity<int>(i).Select(g).Select(f); var right = new Identity<int>(i).Select(x => f(g(x))); Assert.Equal(left, right); }
Again the same admonitions apply: that property is no proof. It does show, however, that given two functions, f
and g
, it doesn't matter if you map an Identity
object in one or two steps. The output is the same in both cases.
F# #
While the Identity
functor is built into the Haskell standard library, there's no Identity functor in F#. While it can be occasionally useful in Haskell, Identity is useless in F#, like 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)
With this type, you can wrap, map, and unwrap values to your heart's content:
> Identity "foo" |> Identity.map Seq.length;; val it : Identity<int> = Identity 3 > Identity 2 |> Identity.map (fun i -> "f" + String ('o', i));; val it : Identity>string> = Identity "foo" > let x = Identity "bar";; val x : Identity<string> = Identity "bar" > Identity.get x;; val it : string = "bar"
There's not much point to this, apart from demonstrating that it's possible.
Summary #
The Identity functor exists, and you can implement it in C# and F#, although I don't see any use for it. Haskell has a type system that can express abstractions such as Functor
in the type system itself. In that language, then, you can define functions that return any type of functor (e.g. Maybe
, Tree
, and so on). If you need a plain vanilla version of such a function, you can make it return Identity
.
The point of this article was only to identify the existence of the Identity functor, and to perhaps illustrate that the concept of a functor encompasses various data structures and behaviours.
Next: The Lazy functor.