在{Professor Frisby Introduces Composable Functional JavaScript中引入了身份仿函数:
const Box = x =>
({
map: f => Box(f(x)),
fold: f => f(x) // for testing
})
我花了大部分时间来理解函子以及为什么上面的JavaScript代码实际上是身份函子。所以我想我会改变它以获得一个真实的"不是身份函子的仿函数。我想出了这个:
const Endo = x =>
({
map: f => Endo(f(x).split('')),
fold: f => f(x).split('') // for testing
})
我的理由是Box,Id_Box: Box -> Box
和Id_Box f = f
。 Endo也会映射到自己,但Endo(f): Endo(x) -> Endo(y)
(如果f: x -> y
)。
我是在正确的轨道上吗?
编辑:
将[{1}}替换为原始示例中的通用string
。
答案 0 :(得分:5)
正如this answer所指出的那样,对于我们作为程序员的目的,我们可以将所有的仿函数视为endofunctors,所以不要过于关注差异。
至于仿函数是,简而言之就是
Array.prototype.map
)xs === xs.map(x => x)
xs.map(f).map(g) === xs.map(f . g)
其中.
是函数组合。就是这样。不多也不少。查看您的Box
,它是一个具有map
功能的数据结构(检查1& 2),该地图功能看起来应该尊重身份和构图(检查3& 4)。所以这是一个算子。但它不会做任何东西,这就是为什么它是身份仿函数。 fold
函数不是绝对必要的,它只是提供了一种“打开”框的方法。
对于一个有用的算符,让我们看一下JavaScript数组。数组实际上做的东西:即它们包含多个值而不仅仅是一个值。如果一个数组只能有一个元素,那么它就是你的Box
。出于我们的目的,我们假装他们只能将相同类型的值保存到简单的事物中。所以数组是一个数据结构,它有一个映射函数,它尊重身份和组合。
let plus = x => y => x + y;
let mult = x => y => x * y;
let plus2 = plus(2);
let times3 = mult(3);
let id = x => x;
let compose = (...fs) => arg => fs.reverse().reduce((x, f) => { return f(x) }, arg);
// Here we need to stringify the arrays as JS will compare on
// ref rather than value. I'm omitting it after the first for
// brevity, but know that it's necessary.
[1,2,3].map(plus2).toString() === [3,4,5].toString(); // true
[1,2,3].map(id) === [1,2,3]; // true
[1,2,3].map(plus2).map(times3) === [1,2,3].map(compose(times3, plus2)); // true
因此,当我们map
函数优于函子(数组)时,我们返回同一函子的另一个实例(一个新的数组),该函数应用于函数(数组)所持有的任何函数。
现在让我们看看另一个无处不在的JavaScript数据结构,即对象。对象没有内置map
函数。我们可以把它们变成算子吗?再次假设对象是同质的(只有一种类型的值的键,在本例中为Number):
let mapOverObj = obj => f => {
return Object.entries(obj).reduce((newObj, [key, value]) => {
newObj[key] = f(value);
return newObj;
}, {});
};
let foo = { 'bar': 2 };
let fooPrime = mapOverObj(foo)(plus2); // { 'bar': 4 }
您可以继续测试该功能(尽可能在JavaScript中)支持身份和组合以满足仿函数法则。