JavaScript 中的函子实现

Functor implementation in JavaScript

我尝试在 JavaScript 中实现一个 Functor。

Functor的定义图如下:

或在 nLab

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

在这里,如您所见,F(f) 表达式在类别图中看起来很典型。

我设法在 JavaScript 中将 Array.map 实现为函子,如下所示:

  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]

A = 1,

F(1) = [1]

不过,虽然我明白F(f)的意思,

F(f) = [f]

至少在 JavaScript 中不会作为函数工作。 .

其实我能想到的合适的方法就是函数组合如:

compose(f)(F).

我也是

FB = Ff([FA])

然而,为了让它起作用,我认为这个表达式只适用于数组,在其他情况下,事情会出错。

所以,这是我的问题。

虽然我理解 F(A)F(B)F(B) 的建议,但事实上 F(A)F(B) 有效,但 [= =12=] 必须是函数的组合不能直接套用?

或者,在范畴论中,它是否允许将 fg 的函数组合表示为 g(f) 含蓄地??

要在JavaScript中组合两个函数,我可以想到三种方法。

第一种方法

最简单的方法是在第二个函数的调用中调用第一个函数。

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

这只需要定义两个函数并提供所需的正确参数

第二种方法

您在问题中提出的 componse 函数方式,您在其中生成一个结合了两个函数结果的函数。我会在这里稍微简化一下。

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

第三种方法

此方法会回答您所需的语法 (F(f))。您可以声明一个特殊版本的外部函数 F,它要么接受一个原始类型参数并立即计算结果,要么接受一个函数参数和 returns 另一个结合了两者执行的函数。您可以通过定义以下函数生成器来完成此操作 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))

我知道第三种方法需要将你的数学函数定义为特殊函数,但如果你愿意这样做,你将能够使用所需的语法在两个方向上执行组合。

这是一个例子:

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

最后更新:

您实现的实际上是 (a -> b) -> a -> [b],但您自欺欺人地认为它是 (a -> b) -> [a] -> [b](详情请见下文)。

观察 Javascript,评估

[Int] * Int

会 return 支持 Int,这可能是所有混淆的来源。


在类型设置中,F(f) 只有在您的类型系统允许类型变量像

一样“通用”时才有意义(例如在 Haskell 中)
F :: a -> [b] 

其中 a 可以是 Int(Int -> Int) 或任何东西,b 也是如此。而你正在寻找的可能是 a 的情况 (a0 -> b) -> a0:

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

with b being a0 here, where F . f where

where F . f where a0
(.) :: (b -> c) -> (a -> b) -> a -> c

c 为 [b],a 为 a0

注意这不同于

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

对于 List 来说是 fmap,而您所知道的 Functor(至少在 Haskell 中实现)只是存在一个 F 的任何类型函数

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

另请注意,此 FF 在您的示例中作为函数存在于完全不同的抽象级别上。 (实际上在 Haskell 中你甚至不能将 F 定义为一个函数。)


另一方面,在无类型的 lambda 演算等中,只要您写得更明确,这是可能的,例如

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

以上是从编程语言理论的角度。


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

据我了解,范畴论是广义函数论。它不关心语法。

如果你想通过编程语言的实现来表达范畴论中的一个概念(例如仿函数),那么客观上它几乎归结为语言特性,主观上归结为语言用户。

JavaScript 数组的仿函数实现是 Array.map,它接受数组元素上的函数并生成数组上的函数。如果 f 是某个函数,那么,就您的图表的分类语言而言,F(f).map(f)(请原谅我滥用符号)。

在图中,恒等式和组成并不意味着应该如何实现函子抽象。相反,图表表达的是函子定律:具体来说,.map(id) 必须与 id 相同,并且 .map(f).map(g) 必须与 .map(compose(f)(g)) 相同.

我这里回答了很多,都不满意,自己解决了。

根据 nLab 中的图表:

这里的X,Y,Z其实就是恒等态射

所以 X,Y,Z 和 f,g,h 的类型是相同的。

因此,我们可以一致地写F(f)F(X)

根据这个 QA 流程,虽然很多人从未提及并且不同意我最初的回应,但这似乎是一个众所周知的事实。

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

从范畴定义single-sorted来看,对象X,Y,Z是恒等态射