好几个星期以来,我一直对仿函数(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左右定律所导致的扁平化过程使事物具有关联性,那么这些身份定律和关联性是否会以某种方式彼此隔离?
答案 0 :(得分:3)
使用const r3 = v.map(take1)
,您已经进入了地图的一层,您可以输入函子(如果愿意)。在函子内部,您具有纯净的未包装值。但是take1
本身尝试再次对这些值使用map
! 可以起作用,但前提是值本身是函子值,例如嵌套的Identity
。
要使用内部已使用map
的函数,只需将 apply 应用于函子值:
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());
答案 1 :(得分:2)
让我们尝试整理术语。
在范畴论中,范畴 C 由对象和态射(也称为箭头)组成。 C 和 D 两类之间的函子 F 记为 F:C-> D ,它映射< em> C 表示 D 的对象,而 C 的词素则是 D 的词素。
您可以(显然)组成函子。
函子的组成是关联的:给定 F:C-> D , G:D-> E 和 H:E-> F ,它们的组成(从 C 到 F 的函子)不需要括号。
您也可以撰写词素(在类别内)。态射的组成也具有关联性。
此外,函子必须尊重态射的构成(即 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 ”通过“逐点”应用从一个数组映射到一个数组。这是Java数组中的 map : Array(f)=(x => x.map(f))(使用=>
作为函数)。 / p>
请注意, Array 并不是您完全可以用Javascript写下的内容。
现在,此函子可以很容易地计算出态素(Javascript函数)等的组成。而其中的一个中间步骤就是 x.map(f).map(g)= x.map(y => g(f(y))(非正式地,重要的是,首先将 f 应用于数组 x 的所有元素,然后再对所有元素应用 g ,或者立即将 g 应用于 f 之后的所有元素)。
如果我们将其与其他函子组合在一起也将具有关联性(尽管我们还没有Javascript中其他函子的示例)。
现在让单子离开画面。
有帮助吗?