Thinking about haskell functors in .net
I’ve been teaching myself haskell lately and came across an interesting language feature called functors. Functors are a way of describing a transformation when you have a boxed container. They have a generic signature of
('a -\> 'b) -\> f 'a -\> f 'b
Where f
isn’t a “function”, it’s a type that contains the type of 'a
.
The idea is you can write custom map functions for types that act as generic containers. Generic containers are things like lists, an option type, or other things that hold something. By itself a list
is nothing, it has to be a list OF something. Not to get sidetracked too much, but these kinds of boxes are called Monads.
Anyways, let’s do this in C# by assuming that we have a box type that holds something.
public class Box\<T\>
{
public T Data { get; set; }
}
var boxes = new List\<Box\<string\>\>();
IEnumerable\<string\> boxNames = boxes.Select(box =\> box.Data);
We have a type Box
and a list of boxes
. Then we Select
(or map) a box’s inner data into another list. We could extract the projection into a separate function too:
public string BoxString(Box\<string\> p)
{
return p.Data;
}
The type signature of this function is
Box-\> string
But wouldn’t it be nice to be able to do work on a boxes data without having to explicity project it out? Like, maybe define a way so that if you pass in a box, and a function that works on a string, it’ll automatically unbox the data and apply the function to its data.
For example something like this (but this won’t compile obviously)
public String AddExclamation(String input){
return input + "!";
}
IEnumerable\<Box\<string\>\> boxes = new List\<Box\<string\>\>();
IEnumerable\<string\> boxStringsExclamation = boxes.Select(AddExclamation);
In C# we have to add the projection step (which in this case is overloaded):
public String AddExclamation(Box\<String\> p){
return AddExclamation(p.Data);
}
In F# you have to do basically the same thing:
type Box\<'T\> = { Data: 'T }
let boxes = List.init 10 (fun i -\> { Data= i.ToString() })
let boxStrings = List.map (fun i -\> i.Data) boxes
But in Haskell, you can define this projection as part of the type by saying it is an instance of the Functor
type class. When you make a generic type an instance of the functor type class you can define how maps work on the insides of that class.
data Box a = Data a deriving (Show)
instance Functor Box where
fmap f (Data inside) = Data(f inside)
main =
print $ fmap (++"... your name!") (Data "my name")
This outputs
Data "my name... your name!"
Here I have a box that contains a value, and it has a value. Then I can define how a box behaves when someone maps over it. As long as the type of the box contents matches the type of the projection, the call to fmap
works.