【问题标题】:Functor implementation in JavaScriptJavaScript 中的函子实现
【发布时间】:2020-08-13 07:39:05
【问题描述】:

我尝试在 JavaScript 中实现 Functor。

Functor的定义图如下:

或在 nLab 中

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

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

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

  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) 有效,但 F(f) 不必是不直接应用的函数的组合?

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

【问题讨论】:

标签: javascript haskell functor category-theory


【解决方案1】:

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

在图中,标识和组合并不意味着应该如何实现函子抽象。相反,这些图表达的是函子定律:具体而言,.map(id) 必须与id 相同,.map(f).map(g) 必须与.map(compose(f)(g)) 相同。

【讨论】:

    【解决方案2】:

    最后更新:

    您实现的实际上是(a -> b) -> a -> [b],但您欺骗自己认为它是(a -> b) -> [a] -> [b](有关详细信息,请参见下文)。

    在 Javascript 中观察,评估

    [Int] * Int
    

    会返回一个Int,这可能是所有混乱的根源。


    在类型化设置中,F(f) 只有在您的类型系统允许类型变量“通用”时才有意义(例如在 Haskell 中)

    F :: a -> [b] 
    

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

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

    b 在这里是a0,当你在哪里有F . f 时会发生这种情况

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

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


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

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

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


    或者,在范畴论中,它是否允许将 f 和 g 的函数组合隐式表示为 g(f)??

    据我了解,范畴论是泛化的函数理论。它不关心语法。

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

    【讨论】:

      【解决方案3】:

      在 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,它要么接受一个原始类型参数并立即计算结果,要么接受一个函数参数并返回另一个结合了两者执行的函数。您可以通过定义以下函数生成器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

      【讨论】:

      • 谢谢。第二种方法是简单地取消函数。第三个是我们切换的行为取决于值:函数或值。
      • 没错,对于第三个,您可以随意使用f(x)F(x)F(f)(x)f(F)(x)
      • 其实我也想过这个想法,我知道这是错误的。问题是AX 可以是函数,尤其是在范畴论中,它可以是任何东西。 .在这种情况下,您的方案不起作用。为简洁的结构提供条件是非常危险的。
      • 谢谢,但我需要的是学术知识,而不是代码破解。至少对我来说,F(f) 是一个带函数的函数,仅此而已。
      • 我以为AX 代表一组数字或数字空间,你是说你可以遇到F(f)(x) 的情况,其中x 也是一个函数?如果是这样的话,也许你可以把已经组合好的函数组合起来,但我不知道兔子洞有多深。坦率地说,我没有那么深的数学知识,而且看起来你的问题比我想象的要复杂得多。
      【解决方案4】:

      在我这里有很多答案,对他们不满意后,我自己解决了。

      就 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

      从范畴的单排序定义来看,对象X,Y,Z是恒等态射。

      【讨论】:

        猜你喜欢
        • 1970-01-01
        • 1970-01-01
        • 2018-09-25
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2012-01-07
        • 2014-09-01
        相关资源
        最近更新 更多