No. I don't know why the article talks about ordinary functions on values as if they're functors. They're not (or at least, not in a nontrivial way; you could come up with a silly, useless way in which they are, but that's not what anyone's talking about). Sorry, but it's the article that's misleading you here.
Roughly speaking, in the programming context, a functor is a generic type with a map operation. E.g., arrays; you can map a function over an array by applying it to each element.
Since there's only one category in play here, these functors are endofunctors of that category; one doesn't normally say "endofunctor" in this context since it doesn't convey any additional information. (As opposed to "a monad is a monoid in the category of endofunctors", where you want to specify that, because that statement is true beyond just the programming context.)
The whole type thing is just something Haskell does, it's not something that's meaningful for general categories.
The whole distinction between value-level or type-level is meaningless in general categories. Sure there's a distinction between morphisms between objects and functors between categories but you've also got the category of categories where the morphisms are functors between categories.
Edit: I'll have to concede that the article confuses them too much though, you can't just pick a random function and declare it to be a functor, especially when it's not clear between which categories.
No.
> And an endofunctor is just one of those where the argument type and return type are the same?
Yes - "endo" means that in general. E.g. endomorphism.
> Sometimes it makes me mad that so much confusion (and as another commenter put it, gatekeeping) has been sown in FP circles through the invention of pointlessly-obfuscated terminology for everything (and through - among many in those circles - a lack of interest in connecting the very simple dots that would allow masses of people to understand these concepts). It's like when the medieval Catholic Church refused to print a bible in anything but Latin so that they'd remain the sole authority on its content.
> Kudos to the OP for cutting through some of that, but shame on those who maintain this status quo.
> Edit: assuming it really is that simple. Some in the comments have suggested parts of the OP were wrong, so I'm curious to see discussion.
The precise terminology is vital; it's how we avoid these errors when talking about this very abstract stuff. Given that the OP is very wrong, precisely because of the kind of attitude you've taken here, I hope you'll take this opportunity to reconsider your views.
Your behavior is precisely the behavior that someone new to FP would see and say "Hard pass on that", which goes into the greater problem of FP being unapproachable to beginners because of the unnecessary gatekeeping. If you cannot communicate with a beginner in language they understand, A.) you don't really understand it either, B.) you're just pushing them away from whatever you're advocating.
This is true, but the problem is that people will always prefer the explanation that is, as the saying goes, "simple, neat, and wrong" over the explanation that is more complicated, especially if they (and possibly the person explaining) aren't aware that the simplified version is wrong. Even if one starts from an accessible viewpoint, any concept that is worth isolating as a concept will involve some difficult step in its exposition, and it is at that step that the person seeking to understand—not the explainer—has to decide whether to stick with the explanation despite the difficulty, or to be satisfied with an easy and wrong explanation.
But the parent post https://news.ycombinator.com/item?id=28476325 to which lmm was responding https://news.ycombinator.com/item?id=28476569 said "Wait, a functor is just a pure unary function?" If someone asks you the definition of a technical term, you sort of have to at least refer to the technical term.
Learn You a Haskell mentions functors in chapter 8 and teaches them properly in chapter 11.
http://learnyouahaskell.com/making-our-own-types-and-typecla...
Regardless of how LYAH chooses to order things, (when have you used a Zipper last time?), they are too essential to any beginner Haskell program.
In my opinion we need a new imperative first approach to Haskell teaching, to develop intuition on monads quickly.
You could not really explain JIT compilation to someone not knowing anything about computers, could you?
You can't explain the concept of doing something only when you absolutely need to, to someone that doesn't know about computers?
I mean, it may as well refer to eagerness, which is something completely different from JIT compilation. You didn’t even say anything about what the compilation is about (which again, requires some knowledge on computers), nor about the reason for doing that.
It's actually insane how hard programmers want to gatekeep even basic concepts.
Like, you can talk about programming to a child, but still not about JIT compilation.
This attitude justifying gatekeeping reminds me of Stack Overflow - mods tired of seeing the same wrong questions asked. But it’s a Q&A site and the purpose is for people to get answers to their questions.
A student can definitely have an attitude that prevents them from learning effectively though. If anyone wants to see the use of correct terminology as "gatekeeping" then they are of course free to do so but the obstacles to learning the subject that such a mindset creates are then a logical consequence of their actions and entirely their own fault.
If you want an example of what happens to a Q&A site without any gatekeeping, look at Quora.
Now, I would say you showing up and openly disparaging both the blog and the person you responded to qualifies as "guns blazing".
The debate is about using language as a gatekeeping mechanism. The analogy is spot on...
> "shame on those who maintain this status quo."
A person shows up eager to learn, expresses gratitude, feels scorned at all of the language-based gatekeeping that goes into this topic. You don't think it's fair to feel frustrated with the people doing the gatekeeping? (I don't think _you_ will, but only because I think you're one of the gatekeepers the original poster is (fairly) shaming).
They expressed gratitude for those who told them easy falsehoods and scorn for those who told them harder truths, characterizing such honesty as "gatekeeping". That's not the action of someone eager to learn, it's someone who's eager to avoid learning, like those politicians who, rather than put in the effort to understand the economic implications of their policy, go shopping around economists until they find one who gives them the answer they want.
Can you paste me in your post where you did this? (Note: you can't). I'll even paste your response, so you can remember better:
>> Wait, a functor is just a pure unary function?
> No.
>> And an endofunctor is just one of those where the argument type and return type are the same?
> Yes - "endo" means that in general. E.g. endomorphism.
>> Sometimes it makes me mad that so much confusion (and as another commenter put it, gatekeeping) has been sown in FP circles through the invention of pointlessly-obfuscated terminology for everything (and through - among many in those circles - a lack of interest in connecting the very simple dots that would allow masses of people to understand these concepts). It's like when the medieval Catholic Church refused to print a bible in anything but Latin so that they'd remain the sole authority on its content.
>> Kudos to the OP for cutting through some of that, but shame on those who maintain this status quo.
>> Edit: assuming it really is that simple. Some in the comments have suggested parts of the OP were wrong, so I'm curious to see discussion.
> The precise terminology is vital; it's how we avoid these errors when talking about this very abstract stuff. Given that the OP is very wrong, precisely because of the kind of attitude you've taken here, I hope you'll take this opportunity to reconsider your views.
Where in this block did you give them "harder truths"? I see "No", "Yes", and then saying "reconsider your views". Again, the purpose of your post clearly wasn't to help, provide value, enable, but instead to disparage. Or, you know, gatekeep.
> They said "It's like when the medieval Catholic Church refused to print a bible in anything but Latin so that they'd remain the sole authority on its content." and concluded with "shame on those who maintain this status quo."
And as I said before that:
> someone who comes in with guns blazing doesn't get to complain about being met with hostility
It's not "gatekeeping" when there's no effort to engage productively in the first place.
> Can you paste me in your post where you did this?
Is "No but..."
So we're just saying things for the sake of it now, I guess.
I don't care to show you where I did something I never claimed to have done in the first place.
> So we're just saying things for the sake of it now, I guess.
As far as I can tell that's what you've been doing the whole time. It's the height of hypocrisy for you to complain that "the purpose of your post clearly wasn't to help, provide value, enable, but instead to disparage."
For example: I might explain the programming concept of a function as "like the equations you remember from math class" (for pure functions) or "like a recipe" (for imperative functions).
I would also not, for example, make up a totally new term for a concept people already know about that just happens to be in the context of my new system of ideas. This actually comes up a lot in my day to day software engineering work: I avoid creating new abstractions in general, and when I do I strongly avoid introducing new terminology in the naming, and when I do I document and explain it to death in the comments, using analogies wherever possible.
There are lots of opportunities where I could come up with a whole new concept with a beautiful new name that nobody understands, and I explicitly choose not to.
Does what I write above bother you at all? Do you feel that it apply to you and programmers in general? Because that is the kind of generalization you are doing to another group of programmers.
I see a lot of functional programmers spending a lot of time trying to teach others. I also see a lot of people not wanting to spend any time learning so they just talk down on functional programmers, and I think its unnecessary.
I'll admit my emotions got a little heated. I get really frustrated thinking about all the potential out there that gets wasted just because of gaps in communication (willful or otherwise), and not because of capacity for understanding. And this very comments section demonstrates the unwillingness by at least some members of the FP community to put things into terms that would help with that problem, so I do feel justified talking about the community as having an issue. Whether they represent a majority or a vocal minority is not something I can say with confidence, though I still feel like I qualified my original statements sufficiently.
In my experience concepts like Functors, Monoids, Monads etc. are difficult, not because they are complicated, but because they are seemingly too simple. A Functor is pretty much something you can map, and every programmer knows list.map, so what's the point? The point is that when you have some infrastructure around using them, then you can see new ways of using them, and you can see that mapping makes sense over a lot more structures than lists. Even over something like a function "a -> b". The problem is that non-functional languages doesn't have this infrastructure around them, so they are not very useful, and so its difficult for people not using functional languages to understand what the point is. I dont really think there is any way to get past this unfortunately and the only way to get comfortable with the terms is to program in a functional language.
> Not all functional languages have typeclasses or an alternative and convenient way to express these concepts, and in those, it's not so important to understand these notions abstractly.
This is true only in an especially strict sense. Haskell's typeclasses provide two basic mechanisms: one for associating a dictionary of data with a type, and one for automatically picking / deducing such a dictionary based on a given type. Only the latter is specific to Haskell (and Scala); the first can be performed even in Java, but you have to explicitly pass the desired dictionary (called a "trait") at all call sites. [0]
I legitimately use traits all the time in Java, and it even has some built-in ones, like Comparator. There's a great paper on the same fundamental technique at [1] (there called "object algebras").
[0] https://www.haskellforall.com/2012/05/scrap-your-type-classe...
[1] https://www.cs.utexas.edu/~wcook/Drafts/2012/ecoop2012.pdf
For example, Java lacks higher-kinding which is needed to define a monad abstractly. This can be got cheated around using ideas from this paper (0), but it costs a cast which the compiler cannot prove safe. This is the basis for things like the arrow library in Kotlin, which while impressive, doesn't compare to the ergonomics of Haskell and still misses a bunch of standard abstractions such as Traversable, which are part of the mentioned infrastructure that makes monads (1) so useful to Haskellers.
[0] https://ocamllabs.io/higher/lightweight-higher-kinded-polymo...
[1] applicative functors more generally.
From the ancient Greek "endo" meaning "within/inside" (as in "within the same group of things").
See also endogenous (endo + genesis, the latter the ancient Greek for born), meaning "born/created inside".
No, although a pure unary function could be viewed as a functor, the other direction doesn't hold.
The common FP definition of a functor is anything that supports a notion of `map` (subject to the restriction that `map`ping the identity function does nothing, this is why a Functor is slightly more than just a Mappable). This is not a unary function although map takes a unary function as an argument.
The more general mathematical definition is a mapping between categories. Categories are sufficiently general that you can view almost anything as a category, which is why a unary function can be thought of as a functor (because a functor can be a mapping between categories that happen to be sets).
But a functor is both more general than a unary function and, in a certain sense, preserves more structure than a function, since it must preserve the structure of the underlying categories whereas functions generally only care about sets.
Explain that again?
ie: say I have some value `f` that is a functor. If I call `map identity f` I should always get back `f`. The mere existence of a `map` function doesn't imply this law holds.
The identity function always returns it's argument unmodified. (identity x = x)
(Now, in Haskell the type system does not encode that law, but not following it would be breaking the interface.)
If this explanation doesn't make sense or you need more examples I'm happy to expand
EDIT: Both me and grandparent forgot another law for functors. They also need to preserve composition.
fmap (f . g) == fmap f . fmap g
In english, if I combine the functions f and g and map them, it should be the same as if I first map g and then map f
I didn't state this because this generally comes for free, assuming no shenanigans such as runtime type reflection/type-casing (but in that case all laws go out the window). In particular in Haskell (which is what I assume you're coming from given fmap and your ML-ish syntax) this is a superfluous law. If a type constructor's `fmap` obeys the identity law, it must obey the composition law.
This sentence just taught me more about functional programming terminology than 4 years of university and 20 years of casual blog-post reading :O
> fmap (f . g) == fmap f . fmap g
Can you give an example where that doesn't follow from `map identity f == f`? Assuming of course `f` .. is what I think is called 'pure'? i.e. is deterministic on its input arguments.
For example, caches whose eviction calculation is triggered by function invocation aren't functors, even if you can map over the elements in the cache (since just calling any function on the cache can cause certain elements to be evicted). That's a bit of a weird one because caches also usually have some time-dependent behavior (which in general makes notions of things like equality a bit hard to pin down and so moots a lot of laws), but you can also have caches with eviction policies that are not based on time strictly, but rather deterministic heuristics that use the number of function calls as an input to the heuristic.
Another example is certain tree-based data structures that use function calls to rebalance themselves in a way where the rebalancing causes publicly observable changes. Again there just calling map by itself can trigger a rebalancing, regardless of whether identity is passed or not.
Another example is stuff like variants of `Iterator` where just iterating over something creates changes or function calls are logged.
FWIW all these cases of a mappable non-functor can be changed into a mappable functor, simply by "suspending" calling the function until you actually ask for a value (so the `map` call does nothing other than just squirrel the function away somewhere). So often the idea of a mappable non-functor isn't useful so much for truly distinguishing a functor from a non-functor, as it is for pointing out how you should implement `map` in a way that "makes sense."
For example, ruby's hash "map" returns an array instead of hash. That's not really a map, is it?
Making this useless distinction between "things with map function" and "functors" serves no purpose. It should be "things with real/fake map functions".
The name of the function does not matter!
interface Mappable<A> {
// Interfaces don't quite cut it here, because you want
// `map` to return a specific implementation of
// Mappable, not a general Mappable, but we'll let that
// slide for the purposes of exposition
map<B>(f: A => B): Mappable<B>
}
class MyWeirdMappable<A>(val myCounter: Int, val myValue: A) implements Mappable<A> {
// Notice the return type is MyWeirdMappable not
// Mappable, as mentioned earlier
map<B>(f: A => B): MyWeirdMappable<B> {
if (myCounter == 0) {
return this
} else {
return MyWeirdMappable(myCounter + 1, f(myValue))
}
}
}
Although this fulfills the contract of `Mappable`, it's not a `Functor`, because `map(identity)` doesn't always return the same (or equivalent) `MyWeirdMappable`.So, to make this absolutely clear, a monad is a type constructor in functional programming. This is why I personally prefer the more “creative” explanations of monads, because they attempt to explain what the monad is doing to the original type: adding context, or the possibility of a null value, or multiple values, or a probability distribution over values, etc.
Now explain what a "type constructor" is, otherwise these words are worthless.
For example, in C, if you have any type Foo, the pointer operator * will give you a new type Foo*.
Another example: in Haskell, Maybe is a type constructor, since it takes a type parameter a (Maybe a). So 'Maybe' is a type constructor, but 'Maybe Int' is a type.
So a type constructor is like a function, but it operates on types, not on values.
"You can think of a type constructor like a generic type, particularly the way those work in languages like TypeScript"
Edit: The C example was added after my initial comment, and is better than nothing, though I'd tend to say the pointer example is a bit of a weird one to go with. Of course intuitiveness is relative and I'm not saying an example has to mention TypeScript or whatever to be valid. But I do think, like, C++ templates would be a somewhat clearer comparison to make for that audience.
Do you mean declarator?, As in:
Foo * f; // f is a pointer to a Foo
The operator dereferences something - it certainly does not create a new type. And neither does the above - the concept of pointers to addressable types is innate to the C and C++ type systems - if you create a type Foo, you get pointers to Foo for free.List<Integer> is a type.
List<T> is a type constructor.
In order to get a real type from a type constructor, you need to "pass it type parameters". In the example above, you need to say that T is Integer.
Values are things. Concrete. Since we are manipulating computers, a value is really just some abstract symbols--1s and 0s occupying a slot in memory. The meaning of the symbols exists on a higher level, what we usually call semantics.
Types are a well-studied method to attach semantics to blocks of otherwise meaningless (but concrete!) symbols. By declaring a variable (which is just a pointer to a memory block containing arbitrary symbols) is of type Int, we are establishing that in the context of the program, the symbols occupying that memory block are to be interpreted as the encoding of an integer.
Functions are transformations of values. They manipulate concrete things to produce other concrete things. By specifying the input and output types, we ensure that the transformations applied by our functions are only used on the right kinds of things, and we also know what kind of thing we will get out of the function. Mind you, these things are still all just arbitrary symbols in blocks of memory, but now we know how to semantically interpret them based on the function's specification.
Type constructors are also transformations, but of types. That is, we use type constructors to modify or enhance the semantics of a basic type. Functors are essentially type constructors that extend function semantics as well: if F<-> is a functor, then you can "lift" any function from type A to type B into a function from type F<A> to type F<B>. (Note that I'm using the commonly recognized "generic type" angle bracket notation to deliberately avoid the confusion of Haskell's pedantically minimalist syntax.)
I think this description provides a much more intuitive path to the understanding of, say, a list as a functor. A "list" is a generic structure that can be applied to any semantics, evident in the phrase "list of X". The preposition "of" makes it clear that this is a semantic extension of whatever kind of thing that X is. Furthermore, there is an obvious way to lift functions: just apply the function to each element of the list, and collect the results in a list. Bam! There's a functor. This is starting to feel like blog post territory, but I'll forge ahead...
An important point here is that the "semantic structure" that type constructors add to a basic type has to come from some extra data that is used when defining the type constructor. In the case of functors, we have to actually describe an algorithm that implements the lifted function in terms of its basic version (as we just did for lists).
So a monoid is also a type constructor, and it must come along with a description of the monoidal product: For type M<A>, we need to describe a function (the monoidal product) which takes two A's and returns another A. We must also identify a specific value within type A that serves as the identity element. Now, to actually be a monoid, the monoidal product and identity element must satisfy some laws, but in general proving the laws are satisfied by a given constructor is outside of the capabilities of our type syntax. This is a step up from the functor type constructor, which needs only a valid implementation of the lifting function (called `fmap` in Haskell) in order to establish that we have actually created a functor.
In any case, to arrive at monads we don't actually combine the functor and monoid type constructors; instead we gently introduce monoid semantics on top of a functor. We do so by making the functor type constructor itself act like a monoidal product, so that repeated applications of the type constructor don't create "nested" versions of the semantics, they just stop at the first level instead. Lists are again a very straightforward example of this: List<A> is a list of things of type A, and List<List<A>> is a list of lists of As, and there is an obvious way to bring the list of lists back down to just a list: flatten it.
...okay maybe I'll stop there, at least for now.
I really appreciate this. I know the Haskell syntax kindasorta, but my brain just doesn't grok it well. Vs anything along the lines of Generic[Type] or Generic<Type> is just far more familiar for most audiences, myself included.
The people whom Haskell type syntax makes sense for are largely not the ones trying to learn this stuff.
> So a monoid is also a type constructor
Is it? You claimed correctly a couple paragraphs back that a functor is a type constructor F bundled with the ability to "lift" functions into the context of F. So `List` is a functor in the usual way, and so on. But a monoid cannot be a type constructor, or else `String` could not be a monoid. A monoid is a concrete type M bundled with multiplication and unit on that type.
I would say "sigh", but you have to say what you mean.
A functor is a PAIR, please notice the emphasis, of a type constructor F and a map function with the following signature: a -> b -> f a -> f b.
Its not a type constructor, it's a pair of two things, the type constructor AND its map function.
Now that we said what it is we provide the usual simple example of Array, Record or Option, show the type constructors and show how we map them.
Is it really so hard to grasp starting from definitions rather than examples and contrived blogs?
I wish people understood that fp is much easier to understand by reading definitions.
The real landscape of its adoption begs to differ. For the great majority of people, examples and analogies are fantastically more efficient for communicating ideas than building up from first principles. You get to re-use existing mental machinery, intuition, etc, until enough experience has been gained to refine it.
which covers basically all the fundamentals to be proficient in functional programming that has definitions that are opaque and unapproachable. And all of those have very simple examples, both in real life and especially programming. But definition has to come first, or you have this great mass of people that do not understand what they talking about even when they blog about it.
If you can take your time to understand that a type constructor (and take the time to see what it is) and a map function form a pair called functor, you are equipped to learn about monads.
It's not rocket science, but yes, it requires a bit of studying and learning, which is what people don't want to hear, and instead choose the long road of contrived example-driven, inaccurate and confusing blog posts.
(a -> b) -> F a -> F b; the parentheses are critical. I don't fully disagree with your point, but if you're going to emphasize rigor and definitions, it would be wise to make sure you're not setting people on the fence up for mistakes akin to those in the original post.
(You also called it `F` and then used it as `f`.)
The equivalent signature in Java (if we could abstract over type functions) would be:
interface Functor<F<_>> {
<A, B> Function<F<A>, F<B>> map(Function<A, B> f);
}
You'll find very similar in Scala's `cats`, up to some currying and reordering. [0]I wish more FP writers understood that not everyone has the same neurotype, and "easier to understand by reading definitions" is far from a universal statement.
So why can't we just call the damn things 'Type constructors'?
So basically it is just a parametric type such as List<T> which has a type constructor so you can get a concrete type (eg List<String>), a map function which takes a function f: A => B and and turns it into a function List<A> => List<B>, and a "flatten" function with will turn a List<List<A>> into a List<A>.
The last part is what allows "flatMap" which takes a function f: A => List<B> and turns it into a function List<A> => List<B> and is really just a shorthand for doing map and then flatten.
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
- 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..
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).
Class<List<String>> thisistypeconstructor(Class<String> strClass)
And if I generify the types above, maybe something like
<OutputType> thisistypeconstructor(<InputType>)
template<template<class> class F, class T>
using apply = F<T>;
static_assert(is_same_v<apply<list, string>, list<string> >);This is why people say "a monad is a monoid in the Category of endofunctors" as a joke whenever anyone posts an unnecessarily complicated explanation. This is the biggest and most visible flashpoint between people who don't understand, and the people who don't understand why they don't understand and think that defining things in terms of other things that you also don't understand is a viable strategy.
I share the sentiment that FP jargon can be impenetrable to the uninitiated, but the same can be said about programming jargon to non-techies: it may sound like gatekeeping to talk about "closures" but there's technical reasons why closures are called closures and global state being accessed by functions are not.
The same goes for FP. Saying `val => val + val` is a functor because it maps numbers to numbers isn't really correct: you could arguably only say that in an universe where `val + val` is the only possible operation, and even then, that's not really correct because the utility of the function is hardcoded (meaning that if we were to hypothetically introduce an axiomatic operation, the internal consistence of this new algebra would implode). In a more useful algebra in a regular universe, the mapping must hold for any valid operation for that type. So a functor for the `number` type ought to look more like something like `Number.prototype.map = function(f) {return f(this)}` (i.e. the mapping contract holds for addition, subtraction, and all other algebraic operations that are valid on numbers contained within `f`)
There are also practical technical things the article glossed over: Array.prototype.map is not monadic. The signature of the callback is `(T, number, Array<T>) => U`, which is most certainly not unary (and for that matter, using JS to illustrate FP is fundamentally problematic since JS isn't side-effect-free to begin with). If you think that's nitpicky, one fundamental thing with FP is that composition relies heavily on algebras not breaking on corner cases. This is why, for example, fantasy-land specifies a different method name for `map` (called `fantasy-land/map`); because `map` is already taken by Array and it breaks some composition patterns (notoriously, `[1,2,3].map(parseInt)`)
> Array.prototype.map is not monadic. The signature of the callback is `(T, number, Array<T>) => U`, which is most certainly not unary (and for that matter, using JS to illustrate FP is fundamentally problematic since JS isn't side-effect-free to begin with).
The unspoken understanding in the OP is that we're only using .map() in the single-argument form. The author makes it clear that only a function with a single argument counts for their definition, and I feel there's also an implicit understanding that that function has to be pure.
The fact that JavaScript technically lets you break both of these assumptions is entirely beside the point. For the sake of teaching, this idealized case is carved out for the example, and I for one had no misconceptions resulting from the relevant quirks of JS. We aren't writing a bullet-proof type system here, we're learning about concepts.
Perhaps I'm getting a different reading from the article than you, but as someone who's gone in and out of this rabbit hole, I found that the author was making some pretty stretchy claims. For example, he says "It may also seem like every monad that is needed already exists. Some common ones are" and proceeds to name Arrays and Promises, neither of which can be used in functional composition beyond the very basic `foo.map(f)` (and may break even then, as per my trivial example above!)
It's fine to try to use a non-functional language to try to explain functional concepts to those who are familiar w/ JS but not FP, but also keep in mind that you may be misunderstanding unknown unknown nuances because of the misappropriation of one domain (JS) to explain another (FP). You yourself are an example of someone who seemed to be about to take a mischaracterized technicality at face value before an avalanche of people started pointing out issues. How do I know to what extent you or someone else understands the implications of functions not always being endofunctors, Arrays not actually being monadic, or why map vs fantasy-land/map matters?
There's a real cost when people come out of reading articles thinking they know more FP than they actually do. This exact discussion about strictness already had implications to the design of Javascript itself: the algebraic consistency argument was brought up while Promises were being specified, but it got dismissed and we ended up with non-monadic promises (and shitshows festered all over the place as a result, e.g. rxjs had to special case them, and we have stuff like fluture.js because why not have multiple future types). The Fantasy Land project itself was created out of spite of a dismissive comment in the promises discussion calling algebraic consistency "fantasy land". On another occasion, one of the React core team members went on twitter at one point saying react hooks are pure (and don't even get me started on algebraic effects). The amount of misunderstanding surrounding FP - even among JS engineers at FAANG - is frankly scary and "loose-is-fine" mentality seeping into the design of widely used APIs is not exactly ideal.
Like, it's fun to communicate in technical jargon -- and personally I'm quite proud of the vocabulary I have built up over time -- but you are totally correct in that it serves as a gatekeeper for an in-group to retain its dominance over a given field. There is a lot to be said for effective communication using a simple, base language and reserving obtuse technical terms for areas where one needs to be very specific.
There's another point to be made for not inventing new technical terms when existing ones apply. The amount of overlapping terminology in this field is infuriating to me.
This is true across all disciplines...
I don't agree that having a domain jargon that is technical and concise is "gatekeeping" for functional programming. If you are a frontend developer you need to know what the DOM is. If you are a Haskell programmer you need to know what a monad is. The fact that you don't get the Haskell jargon "for free" coming from other programming disciplines doesn't mean that it's "gated", it just means you need to learn. And there are plentiful resources to help you with that, countless "how to understand monads" articles and so on. In my experience, the Haskell community is perhaps the single most welcoming and helpful programming community. There's even a handy guide with big, friendly drawings.
It's like, effing pick one! And if you have to have multiple why do they have to overlap with other completely different things? When I first read about a 'map' in this context I was very confused.
You also see this a lot comparing Microsoft environments to everyone else. Reading microsoft docs in the late 90s (which is where I first learned a lot of these concepts) you learn one set of vocabulary, and then in the _nix and webdev worlds you learn a whole different set. Take 'databinding', for example... I have not once heard this term used discussing shadow DOM stuff like React or Vue, yet databinding is exactly one of the services they provide. But no, they have to call it something different, making a lot of prior-art type research much more difficult than it needs to be. Perhaps it is part of the reason why the webdev world seems so intent on pointlessly repeating history. It hampers discussion and contributes to the conceptual isolation and tribalism that seems so common in this field.
(And in the perl example, yes I know it's short for hashtable and that this is because this dictionary-like thing is exactly that, but I think that from an end-user's point of view it would be best if all these different terms were unified)
With that said I may have gone on a bit of a rant here and made some generalizations, and I don't feel good about that aspect, so I apologize.
I think a big part of it is difference in definitions. There's the mathematician's monad, which is pure and abstract and obeys all these laws. And then there's the engineer's monad: an interface you can map, ap, and flatmap over. People get so hung up over "algebraic purity" they forget why they were invented/co-opted for software in the first place: to solve the problem of applying pure functions to side-effecty operations.
> A monad is a wrapper. Anyone who tells you otherwise is being obtuse.
I'd say reading the Bible, or any other deep philosophical/spiritual text in a translation is almost an utter waste of time (unless that actually helps you psychologically, then it is not), if not harmful.
The original words and phrasing with all their possible meanings make a huge lot more sense than specific translations.
Something that sounds banal or weird can actually mean something entirely different and deep once you carefully analyze the original words.
It is, however, important to remember Latin is not the original for the Bibile. But it still has a lot of what has been lost in many other translations.
A functor has two pieces: a pure unary function from types to types, and a pure unary function from functions to functions. The article doesn't talk about the function on types; it doesn't even talk about the function on functions. Its first example of a functor is the function `(num) => "" + num`, which isn't a functor. They map values of Integer to values of String, but they do not map any type to some type, nor do they map any function to some function. Neither part of a functor is present nor nearby.
As an example, for every type T, there is a (non-generic!) type of lists with elements of type T. The non-generic part is really, really important, and is probably a big source of confusion. When we define, say, `class List<T>`, we're defining a whole family of independent types, which we might concretely name List__Integer, List__String, and so on. The List<T> notation is a way of getting that specific type without knowing what its name is. By abuse of notation, we (programmers, not category theorists) talk about List<T> as though that were the name of the type. Theorists separate the two notions, and consider `List` to be an actual function from types T to types List__T, whatever that type actually is.
Most languages that let you define a family of types don't let you refer to the type function on its own. Java (say) never lets you refer to the List function on its own; the name List refers to some other shameful and unrelated concrete, non-generic type. When your language does let you speak about the type function itself, and pass it around to other things, we say that it has "higher-order types". (Just as a language that lets you speak of and pass around a function `factorial` without invoking it has "higher-order functions"; notwithstanding closures, that's what "higher-order" means. [0])
Just having a type function is not enough to have a functor. You also need a function that takes transformations between two types, T and S, to transformations between whatever types the type function sends them to. (For List, we said they're called List__T and List__S, but we can always refer to them indirectly by saying "whatever List maps T to", which is generally written F<T> for any type function F.)
Because of the abuse of notation, we collectively "forget" that `List<T>` defines a function from types to types, and pretend that a functor is just defined by what it does to functions from T to S. But most of the point of functors in functional design is that you can pass around the type-function part, `List`, with the knowledge that it describes a mappable type. If you don't pass around type functions, the concept of a functor will be useless to you.
Though in my experience even the "flash cards" are hard to find on the internet.
There's some magic to these words, but that magic is in how they're used to create new software, real things you can do with them that are easier to talk about when you've got single words for the concepts instead of vague descriptions.
People in Haskell do things like having concatenating to an array be the same syntax as executing two statements sequentially. That's sort of weird, and probably useless, but the whole idea that two sequential statements are basically a monadic operation over the state of your computer, and maybe over the entire world was a new idea in the 90s and it captured something in the formal domain that we hadn't really captured as well before I believe.
edit: I'm not trying to convince you that monads are important or even useful in general, I hope it doesn't come across that way. My favourite programming languages don't have monads as a fundamental concept, Haskell fell from my list a couple years ago. It's an interesting way of thinking about types and computing and I think that makes it more interesting than just a bunch of dense definitions for mysterious looking names.
I'm sure some of it really is warranted, but my experience looking at formal math and logic is that they make things a lot harder than they need to be by introducing terms and symbols that stand-in for intuitive concepts and require unnecessary mapping back and forth between what you're reading on the page and the underlying concepts.
I believe people find monads difficult to grok not because the definition is hard to remember, but because the situation in which it is useful to distinguish the type class is abstract and frankly not super useful in most cases.
Funny thing about zoos of new terminology, back when I was an undergrad I found the type classes to be a confusing subject. Then one day I realized that type and class are basically synonyms so you might as well call them type types or class classes, which totally cemented the idea for some reason. My brain had been getting stuck on this random terminology and this just unclogged it.
They're useful when trying to grok other blog posts or discussions that talk about them (often in service to some further concept that you have no hope of understanding without knowing that first layer of terminology)
> My brain had been getting stuck on this random terminology and this just unclogged it
Exactly! What I'm requesting is that the FP community at large gets more comfortable with the idea of providing those "X is roughly just Y" comparisons for newcomers, so they don't get stuck on all the random terminology and can be "unclogged".
And guess what another name for a unary functions is... 'monadic'.
Functors capture a particular pattern of "pipelining": a series of operations A -> B, B -> C, etc. can be uniformly assembled in the context of any arbitrary functor F, ultimately giving a unary pipeline `F A -> F C`. (I won't detail what this means completely; I'm going for a gut instinct in the following bit.)
But what if you have a binary function, (A1, A2) -> B? Functors only let you assemble unary operators; there's no provision for taking the outputs of two pipelines and weaving them together. Applicatives (or, "monoidal functors") give you an additional operator that let you turn a pair `(F A1, F A2)` into a contextual pair `F (A1, A2)`, which can then be fed to a unary pipeline. (In terms of pipe shapes, it's a Y-bend.)
Monads extend this in a less interesting way, but in the sense of "monadic" as "unary", functors are actually more monadic than monads!
That's only Haskell. The other FP languages have sane communities.
Sometimes it makes me mad that so much confusion (and as another commenter put it, gatekeeping) has been sown in FP circles through the invention of pointlessly-obfuscated terminology for everything (and through - among many in those circles - a lack of interest in connecting the very simple dots that would allow masses of people to understand these concepts). It's like when the medieval Catholic Church refused to print a bible in anything but Latin so that they'd remain the sole authority on its content.
Kudos to the OP for cutting through some of that, but shame on those who maintain this status quo.
Edit: assuming it really is (roughly) that simple. Some in the comments have suggested parts of the OP were wrong (assuming they aren't just being pedantic), so I'm curious to see discussion.