Unfortunately, due to abuse of notation, the type constructor isn't given much focus. But it's true that "type constructor" is the first conceptual hurdle.
The List functor is a combination of the type function sending `T` to `List<T>` and the ... what do you want me to call it, a function function? implemented in Java as:
static <T, S> Function<List<T>, List<S>> map(Function<T, S> f) {
return (ts) -> {
var ss = new ArrayList<S>();
for (final var t : ts) ss.add(f.apply(t));
return ss;
};
}
We call it as map(f).apply(ts). If we want to pass them both at once, we can rewrite the function so that it's not static, but it doesn't much change that it's a mapping operation.But it's not the `map` method that's the important part, so much as the idea that we can (or want to; can't in Java) pass `List` to any function that needs a Functor, which can then use whatever `map` implementation that functor has, without knowing what actual type function it was given.
Really, I suspect it's neither the type constructor nor the map function that's the conceptual hurdle. It's abstracting over the type constructor; taking a type constructor as a parameter. You may have never used a language that supports that, or seen a need for it, and this may all seem like needless sophistry until you do.
- 5 is a value. Its type is int
- int is a type
- T is a type parameter. Like the x in "f(x)", we have to pass one in to evaluate it
- List<T> is a type constructor. It takes a type, T, and spits out a new, "compound" type. It will "contain" T's.
- [3,5,7] is a list. Its type is List<int>. We fed the type "int" to List<T> to construct a new type
- List<T> is a functor because it "contains" T's. List<int> "contains" ints.
- F<T> -> () (a function which takes a T and returns nothing) is a type constructor. It's not really a functor. There's ways in which it technically can be (e.g. decorators in python) but for the sake of pedagogy and giving a counterexample, I'm going to say it's not, because you usually aren't going to map over something like that.
- because functors "contain" other values, you can apply a function to the contents, without changing the container context. This is called map. It may change types, eg mapping sqrt over a list of ints could give floats. But it always gives the same "container type"
The tricky bits:
- To be a true blue functor, there has to exist an identity function that is a no-op and gives you the same types and values back. You needn't define it, it just has to be possible to define.
- functors need not actually "contain" anything, nor the contained value be primitive data. But they always have this wrapper-like feel to them, with an outer type, an inner type, some context/data/value/computation, and the ability to map over that inner value. E.g. Optional<T> could contain nothing, but you can still map over it.
- it's not necessary to be able to dereference or "dispense" the contained value. Sometimes, like the IO monad pattern, the inner value gets "stuck". it never leaves the IO, it's only ever (flat)mapped over.
- the inner value can be: nothing, a single datum, a collection of data, a function, a computation, another functor, another type constructor...almost anything you could assign to a variable.
- well-known functors include: List<T>, Dict<K,V>, Optional<T>, Result<T,E>, Future<T>, Generator<T>, IO<T>, Tree<T>. T* (pointer to a T). Virtually all container types.
Hope that helps
If you have two sources and you want to combine them into a single source, you need an Applicative Functor, also known as a monoidal functor.
If you have a source that itself produces sources, and you want to flatten the nesting and treat it as a single-level source, you need a Monad.
> F<T> -> () (a function which takes a T and returns nothing) is a type constructor. It's not really a functor.
I'm having a little trouble matching the prose against the type signature. A function which takes a T and returns nothing would have type `T -> ()`; no F in sight. I'm guessing you meant that F<T> is defined to give `T -> ()`, in which case F is indeed not a functor; it's a contrafunctor, a place to which Ts can be sent (not retrieved).
- List<T> is a functor because it "contains" T's. List<int> "contains" ints.
Is really correct. I believe a `Container<T>` becomes a functor only if it also 'has' (can have?) a function that can take a function and apply it to all values in the 'container'. So, a 'map' of some sort.
I mean, I see your explanation of 'map' later on. It's also probably true that in real world software engineering contexts any 'container type' of that kind would also be one that either has or can naturally have a `map'. It's just that if someone willing to understand this read your comment and went: "Aha! Some container thing is a functor because it contains things!" I think they'd gain incorrect understanding.
Not that I'm an FP guru, so..