Reputation: 299
I try to implement a Functor in JavaScript.
A diagram of definition to the Functor is as follows:
or in nLab
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
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:
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
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
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
Reputation: 5283
To compose two functions in JavaScript, I can think of three ways.
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
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))
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