So, then you're not familiar with C/C++, Java, C#, Pascal/Delphi, Ada - to say the least.
So, then you're not familiar with C/C++, Java, C#, Pascal/Delphi, Ada - to say the least.
Well, yes, strictly speaking you can do just about anything in just about any Turing complete language if you work hard enough. However, expressing patterns like that with the powerful, general semantics and the elegant syntax of the Haskell version is not something that is offered by any of the other languages you mentioned.
It was perhaps an unfortunate choice of example, because many languages do have first class support for some sort of array/list type and some sort of optional/nullable type and so might offer a reasonably tidy implementation of this specific case. However, in Haskell, the exact same technique would apply just as much to any choices of Functors. Indeed, you can often write generic algorithms that work with any choices of Functors (or Applicatives, Foldables, Monoids, etc, depending on what minimal functionality is required for your algorithm to make sense). Likewise, there are very many generic algorithms already available in libraries that can be used with both the standard instances of those typeclasses and new ones you write yourself. This is the big win for Haskell's type and typeclass system, or what the original author refers to as its emphasis on structure.
They're trying to say that Java doesn't support writing a single function like so:
<T<A> extends Iterable<A>, A, B> T<B> fmap(T<A> iterable, Function<A,B> mapper)
I.e. you can't pass ArrayList<A> to a method which takes List<A> and maps to List<B> and know for certain that you get an ArrayList<B> out.[1] https://en.wikipedia.org/wiki/Type_class#Higher-kinded_polym...
I'm not so sure fmap would make semantically as much sense in something like Java as it does in FP.
You say that, but actually fmap doesn't exist in OCaml (or I assume any other common MLs). Of course you can embed a language and type system within it that does have it (https://blog.janestreet.com/generic-mapping-and-folding-in-o...) but that's different.
OCaml still has currying, algebraic data types, "implicit" returns, tail call optimization, etc., and it encourages recursion instead of loops wherever applicable.
-- zs' = [Just 2, Nothing, Just 4]
zs' = (fmap . fmap) (+1) [Just 1, Nothing, Just 3]
in C? Nullable!int[] zs = [2.nullable, Nullable!int(), 4.nullable];
// no partial application, and
// separate template parameters and runtime parameters
// so (fmap . fmap) (+1) is one function
Nullable!int[] zs2 = map!(apply!(i => i + 1))(zs).array;
The C version probably has a lot more function pointers, and may use nonstandard compiler functions like closures. But in principle you could do the same thing. (fmap . fmap) (+1) [Just 1, Nothing, Just 3]
but also (fmap . fmap) (+1) Just [1, 3],
rather than using (list_map . option_map) (+1)
and (option_map . list_map) (+1)
respectively.The usefulness of this is arguable. In my biased opinion as someone who favours OCaml (which lacks the typeclasses that allow this) over Haskell, I think it's minimal. While it's definitely useful to recognise that lists, options and promises are examples of the same pattern, I don't this generalisation has many benefits in code (at least in MLs where you don't need this pattern just to print things).
And I have argued it on D, and we very deliberately did not go with the range semantics. Optional types are not ranges unless you're compulsively hunting for forced commonality. Might as well treat any value as a range of 1.
For instance, the list type `[a]` and the option type `Maybe a` are both functors. The associated type variable is `a`. This type variable can be manipulated by using `map` to apply some function `f : a -> b` over values of both `[a]` and `Maybe a`. Applying `f` using `map` over such values would yield values of new types, namely `[b]` and `Maybe b`.
This implies that `map f` is a general way of expressing the application of function `f` to values of any functor type. The notion of functor types is therefore supposed to represent any kind of abstract containment relationship. In other words, any time you have the situation of an arbitrary type being wrapped in some kind of "wrapper" (list of..., set of..., range of..., optionally present..., etc), you are dealing with a functor type.
The functor abstraction allows you to apply ordinary functions to such values in a uniform way, regardless of which particular kind of container or wrapper we are dealing with.
Consider this: the functor notion is saying that any thing can be put into a list, that any thing can either be present or missing, and so forth. This is a commonality between the concept of list and the concept of possibly-present-thing (an option type). It also allows you to express some very elegant code in a uniform manner.
This is not making the claim that they are the one and the same operation on some deeper level, just noticing the commonality and presenting a uniform interface based on the commonality.