Functor implementation in JavaScript

Last Update:

What you have implemented is actually (a -> b) -> a -> [b] but you have tricked yourself into thinking that it is (a -> b) -> [a] -> [b] (see below for more details).

Observe that in Javascript, evaluating

[Int] * Int

would return back an Int, which is probably where all the confusion originated from.


In a typed setting, F(f) would only make any sense (e.g. in Haskell) if your type system allows type variable to be "versatile" like

F :: a -> [b] 

where a can be an Int and a (Int -> Int) or just anything and so is b. And what you are looking for is probably the case for a to be (a0 -> b) -> a0:

F :: (a0 -> b) -> a0 -> [b]

with b being a0 here, which is what happens when you have F . f where

(.) :: (b -> c) -> (a -> b) -> a -> c

with c being [b] and a being a0.

Note this is different from

F0 :: (a -> b) -> [a] -> [b]

which is fmap for List, and what you know as Functor (at least implemented in Haskell) is simply any type F for which there exists a function

fmap :: (a -> b) -> F a -> F b

Also note that this F exists on an entirely different abstraction level than the F as a function in your example. (Actually in Haskell you can't even define F to be a function.)


On the other hand, in an untyped lambda calculus, etc, this is possible as long as you write things more explicitly e.g.

const F = a => _.isFunction(a) ? x => [a(x)] : [a] 

The above is from a programming language theory point of view.


Or, in category theory, does it allow to express function composition of f and g as just g(f) implicitly??

As far as I understand, category theory is a generalised theory of functions. It doesn't concern itself with syntax.

If you want to express a concept in category theory (e.g. functor) through an implementation in a programming language then objectively it pretty much boils down to the language features, and subjectively the language users.


The functor implementation for a JavaScript array is Array.map, which takes a function on array elements and produces a function on arrays. If f is some function then, in terms of your diagrams' categorical language, F(f) is .map(f) (please excuse my abuse of notation).

In the diagrams, the identities and composition are not meant to show how the functor abstraction should be implemented. Rather, what the diagrams are expressing are the functor laws: in concrete terms, that .map(id) must be the same as id, and that .map(f).map(g) must be the same as .map(compose(f)(g)).