smooth_writing
smooth_writing

Reputation: 299

Functor implementation in JavaScript

I try to implement a Functor in JavaScript.

A diagram of definition to the Functor is as follows:

enter image description here

or in nLab

enter image description here

https://ncatlab.org/nlab/show/functor

Here, as you see F(f) expression looks typical in category diagrams.

I managed to implement Array.map as a Functor in JavaScript as follows:

  const compose = f => g => x => g(f(x));

  const f = a => a * 2;
  
  const F = a => [a];

  const A = 1;
  const FA = F(A); //[1]
  const Ff = compose(f)(F);

  const FB = Ff([FA]);

  console.log(FB); //[2]

F = a => [a]

when A = 1,

F(1) = [1]

However, although I understand what F(f) means,

F(f) = [f]

won't work as a function in JavaScript, at least. .

In fact, only what I can think of an adequate way is function composition such as:

compose(f)(F).

Also, I did

FB = Ff([FA])

to make it work, however, I think this expression smartly works only for array, and in other cases, things go wrong.

So, here is my question.

Although I understand what F(A) , F(B) , and F(B) suggests, and in fact, F(A), F(B) works, doesn't F(f) have to be composition of the functions not direct apply?

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

Upvotes: 5

Views: 426

Answers (4)

smooth_writing
smooth_writing

Reputation: 299

After I had many answers here and was not satisfied with them, I've resolved by myself.

In terms of the diagram in nLab:

enter image description here

Here, X,Y,Z is identity morphism in fact.

So the Type of X,Y,Z and f,g,h is identical.

Therefore, we can write F(f) or F(X) consistently.

According to this QA process, although many have never mentioned and also disagreed to my initial response, this seems to be a known fact.

https://mathoverflow.net/questions/336533/why-is-an-object-not-defined-as-identity-morphism

https://ncatlab.org/nlab/show/single-sorted+definition+of+a+category

From the perspective of single-sorted definition of category, the object, X,Y,Z is identity morphism.

Upvotes: 0

Archy Will He 何魏奇
Archy Will He 何魏奇

Reputation: 9767

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.

Upvotes: 3

duplode
duplode

Reputation: 34378

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)).

Upvotes: 3

M0nst3R
M0nst3R

Reputation: 5283

To compose two functions in JavaScript, I can think of three ways.

First approach

The simplest way is to call the first function inside the call of the second.

F(f(x)) // -> result

This only requires defining the two functions and providing the correct argument needed

Second approach

The componse function way you proposed in the question where you generate a function that combines the results of the two functions. I will slightly simplify it here.

const compose = (f, g) => ((x) => (g(f(x))));
const Ff = compose(f, F);
Ff(x) // -> result exact to F(f(x))

Third approach

This method answers to your required syntax (F(f)). You can declare a special version of the outside function F that either takes a primitive type parameter and immediately calculates the result, or takes a function parameter and returns another function that combines the execution of both. You can do this by defining the following function generator composify.

const composify = (f) => (
    (x) => {
        if (x instanceof Function) {
            return (z) => (f(x(z)));
        }
        return f(x);
    }
);
const Fc = composify(F);
const fc = composify(f);
Fc(x) // -> result exact to F(x)
Fc(fc)(x) // -> result exact to F(f(x))
fc(Fc)(x) // -> result exact to f(F(x))

I understand that the third method requires defining your math functions as special functions, but if you are willing to do that, you'll be able to perform the componsition with the required syntax and in both directions.

Here is an example:

const composify = (f) => (
    (x) => {
        if (x instanceof Function) {
            return (z) => (f(x(z)));
        }
        return f(x);
    }
);
const addOne = (x) => (x + 1);
const double = (x) => (x * 2);
const composingAddOne = composify(addOne);
const composingDouble = composify(double);

console.log(composingAddOne(3)); // -> (3 + 1) = 4
console.log(composingDouble(3)); // -> (3 * 2) = 6
console.log(composingDouble(composingAddOne)(3)); // -> (3 + 1) * 2 = 8
console.log(composingAddOne(composingDouble)(3)); // -> (3 * 2) + 1 = 7

Upvotes: 1

Related Questions