【问题标题】:Functor associative law confusion函子结合律混乱
【发布时间】:2018-12-23 19:57:28
【问题描述】:

我已经被函子(Endofunctor)的结合律弄糊涂了好几个星期了。

我知道每个 endofunctor 形成组合/关联特征。

组合是关联的。基本上这意味着当你组合多个函数时(如果你喜欢的话,可以使用态射),你不需要括号:

h∘(g∘f) = (h∘g)∘f = h∘g∘f

让我们再看一下 JavaScript 中的组合法则:

给定一个函子,F:

const F = [1, 2, 3];

以下是等价的:

F.map(x => f(g(x)));
// is equivalent to...
F.map(g).map(f);

但是,如下代码所示,尤其是后面的部分:

console.log("===================");
const take1 = a => a //any
  .map(g)
  .map(f)
  .map(trace);

这会导致类型错误:

//  const r3 = v.map(take1); //TypeError: a.map is not a function

,当然:

const take2 = a => Identity(a) //Identity(any)
  .map(trace)
  .map(g)
  .map(trace)
  .map(f)
  .map(trace);
const r4 = v.map(take2);

有效。

我觉得有问题的是在take2函数中,将参数类型:a转换为Identity(a)的要求并不是真正的组合函数。

我也明白 Monads 是为了避免这个组合问题,我想知道这个问题是否仅仅是由于缺少“Monads 的左右同一律”而仍然满足函子结合律,或者可能有不同层关联律,在函子中,显然满足了某一层关联律,但如上例所示,另一层关联律被破坏。

你能澄清一下吗?

示例代码当然是用 JavaScript 编写的,但我仍然标记 Haskell,因为社区在这个主题上很强大,所以请原谅。

谢谢。

const trace = x => {
  console.log(x);
  return x;
};
const Identity = value => ({
  map: fn => Identity(fn(value)),
  valueOf: () => value,
});
const u = Identity(2);
const f = n => n + 1;
const g = n => n * 2;
// Composition law
const r1 = u
  .map(x => f(g(x)));
const r2 = u
  .map(g)
  .map(f);
r1.map(trace); // 5
r2.map(trace); // 5
console.log("===================");
const take1 = a => a //any
  .map(g)
  .map(f)
  .map(trace);
const v = Identity(100);
//  const r3 = v.map(take1); //TypeError: a.map is not a function
const take2 = a => Identity(a) //Identity(any)
  .map(trace)
  .map(g)
  .map(trace)
  .map(f)
  .map(trace);
const r4 = v.map(take2);

PS/编辑:

问这个问题的另一个原因是,如果我们简单地将函数序列 f/g/h 视为数据序列并像字符串一样剪切和粘贴,则结构变为:

h∘(g∘f) != (h∘g)∘f != h∘g∘f

没有刻意展平程序。这破坏了关联性,如果只有 Monoids/Monads 的左/右恒等律的扁平化过程使事物具有关联性,那么这些恒等律和关联律是否不相互隔离??

【问题讨论】:

  • 你问的问题真的与结合律无关。
  • 还有一个建议:要完全进入函数式思维,最好不要使用trace,而只显示最终值。 (并不是说 Haskellers 不使用trace 进行调试,或者更确切地说是traceShowId,而只是作为找出一些价值差异细节的黑客。首先总是类型检查阶段。)

标签: javascript haskell monads functor associative


【解决方案1】:

让我们试着整理一下术语。

在范畴论中,范畴C对象态射(也称为箭头)组成。两个类别CD之间的函子F,写成F: C -> D,映射C 到 D 的对象和 C 的态射到 D 的态射。

您可以组合函子(以显而易见的方式)。

函子的组合是关联的:给定 F : C -> D, G : D -> EH : E -> F,它们的组合(从CF的函子)不需要括号。

你也可以组合态射(在一个范畴内)。态射的组合也是结合的。

此外,函子必须尊重态射的组合(即,F(g∘f) = F(g)∘F(f))。这与关联性完全不同。

endofunctor 是从某个类别到同一类别的函子,F : C -> C

在 Javascript 中,没有类型,因此要使其成为一个类别,请将单个对象(某种通用类型)想象为唯一的对象。态射是具有一个参数的函数。

现在这个

const F = [1, 2, 3];

不是函子:你没有说对象是如何被映射的(虽然没有太多选择),你没有说态射(Javascript 函数)是如何被映射的。

但是,您可以像这样为 Javascript 定义一个仿函数 Array

a) 我们的通用类型被映射到自身。 (如果 Javascript 有类型,我们会将类型 t 映射到类型“t 的数组”)。

b) 一个函数 f 通过“逐点”应用它被映射到一个从数组到数组的函数。这是 Javascript 中数组的 mapArray(f) = (x => x.map(f))(使用 => 表示函数)。

请注意,Array 不是你可以用 Javascript 完全写下来的任何东西。

现在这个函子尊重态射(Javascript 函数)等的组合,因为我们可以很容易地计算出来。其中一个中间步骤是确实 x.map(f).map(g) = x.map(y => g(f(y)) (非正式地说,它没有是否首先将 f 应用于数组 x 的所有元素,然后再将 g 应用于所有元素,或者如果立即将 f 之后的 g 应用于数组的所有元素)。

如果我们将它与其他仿函数组合起来,它也会是关联的(尽管我们还没有 Javascript 中其他仿函数的示例)。

让我们暂时不考虑单子。

这有帮助吗?

【讨论】:

  • “在 Javascript 中,值构成一个类别。”这没有意义,不是吗?该类别中应该包含哪些值?它们当然不是该类别的对象。
  • @leftaroundabout:你是对的,我很困惑,因为没有类型。已编辑。
【解决方案2】:

有了const r3 = v.map(take1),你已经进入了一层map-yness,你进入函子,如果你愿意的话。在函子内部,您有纯的、未包装的值。但是take1 本身尝试再次对这些值使用map可以工作,但前提是这些值本身就是函子值——比如嵌套的Identity

要使用已经在内部使用 map 的函数,只需将其应用到 functor-value:

const f = n => n + 1;
const g = n => n * 2;

const Identity = value => ({
  map: fn => Identity(fn(value)),
  valueOf: () => value,
});

const take1 = a => a //any
  .map(g)
  .map(f);

const v = Identity(100);
const r3 = take1(v);

console.log(r3.valueOf());

【讨论】:

  • 谢谢,但这是我所知道的,我想我已经在问题中澄清了。不幸的是,到目前为止,您没有就这个话题回答任何问题。
  • 嗯,不太清楚你的问题是什么
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2012-06-15
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多