理解函子定律:这是一个函子吗?
Understanding functor laws: is this a functor?
下面的代码写在javascript.
这个问题涉及尝试深入研究一些范畴论,也许 haskeller 或更熟悉这个问题的数学方面的人可以帮助我?
我正在努力思考函子是保留结构的类别之间的映射这一想法。更具体地说——根据我的理解——编程语言中的函子是一个内函子。这意味着编程语言中的仿函数是将类型和函数映射到编程语言中通常定义的更广泛的类型和函数类别中的子类别的态射。
据我所知,函子(或内函子)还必须遵守某些法则,这些法则通过促进组合和同一性来保护结构。
我发现几乎不可能创建一个我认为保留结构并遵守函子定律的函子。由于我只是在 javascript 中真正认真地编程过,所以类型理论是我从未真正考虑过的事情(毕竟 JS 是无类型的)。
我做了这个简单的例子,它把整数提升到一个最小的上下文中,在这个上下文中映射不适用于偶数。换句话说,你可以用你的构图映射,但一旦你击中偶数,节目就结束了。
这看起来有点像 Maybe:
class noEvens {
constructor(x) {
this._val = x;
}
static of(x) {
return new noEvens(x);
}
isEven() {
return this._val % 2 === 0;
}
map(projF) {
return this.isEven() ? noEvens.of(null) : noEvens.of(projF(this._val));
}
}
但很明显,在某些情况下,这不会与应用于普通 JS 类别中的整数的组合交换。考虑一个简单地将整数加一的投影函数。
如果我将一个偶数提升到这个 noEvens 上下文中,然后添加一个,它将给我一个 null 的 noEvens。但是,如果我先将一个加到一个偶数上,然后提升结果,它会导致奇数的 noEvens。
根据我的理解,这两种途径都应该根据函子定律进行交换。他们显然没有,因为通过每个上下文的相同映射在被提升后不会产生相同的结果 "noEvens.of(value)"。
所以我想我的问题是,这是否意味着这不是函子?这种情况(按类型或其他)是什么让它表现得很奇怪?
我想我只是感到困惑,因为似乎所有 "noEvens" 所做的就是将值提升到一个新的上下文(子类别,无论什么)中,其中偶数不存在,但它是很明显,某些路径不会通勤。
我发现 "lifting a value" 进入新映射上下文的想法非常直观,它为您提供了很多处理条件的机会,而无需实施大量冗余代码。但我不想假装我遵守 "functor laws".
的一些正式系统
我在分析这种情况时遗漏了什么类型系统和函子定律?
除了我的评论...
您可能会注意到您的 almost-functor class 也不满足恒等式。
const id = x => x;
new noEvens(2).map(id) // != new noEvens(2)
我的第一个想法是错误是允许首先构造一个包含偶数的 noEvens 对象。如果 isEven 检查改为在构造函数中完成,那么您可以满足 ID 法则。
class noEvens {
constructor(x) {
if (x % 2 === 0) {
this._val = null;
} else {
this._val = x;
}
}
static of(x) {
return new noEvens(x);
}
map(projF) {
if (this._val === null) {
return noEvens.of(null);
}
return noEvens.of(projF(this._val));
}
}
const id = x => x;
new noEvens(1).map(id)._val // ok: 1
new noEvens(2).map(id)._val // ok: null
但是,结果发现这个解还是不满足组合律
const plusOne = x => x + 1;
// fmap f . fmap g == fmap (f . g) ?
new noEvens(1).map(plusOne).map(plusOne)._val // null
new noEvens(1).map(x => plusOne(plusOne(x)))._val // 3
所以最终我认为致命的缺陷是 noEvens 限制了它可以保存的数据类型。正如 Bergi 所说,"A usual functor would be capable of containing any arbitrary data." 所以 noEvens 作为一个概念的核心,不可能是一个遵守合成法则的函子。
下面的代码写在javascript.
这个问题涉及尝试深入研究一些范畴论,也许 haskeller 或更熟悉这个问题的数学方面的人可以帮助我?
我正在努力思考函子是保留结构的类别之间的映射这一想法。更具体地说——根据我的理解——编程语言中的函子是一个内函子。这意味着编程语言中的仿函数是将类型和函数映射到编程语言中通常定义的更广泛的类型和函数类别中的子类别的态射。
据我所知,函子(或内函子)还必须遵守某些法则,这些法则通过促进组合和同一性来保护结构。
我发现几乎不可能创建一个我认为保留结构并遵守函子定律的函子。由于我只是在 javascript 中真正认真地编程过,所以类型理论是我从未真正考虑过的事情(毕竟 JS 是无类型的)。
我做了这个简单的例子,它把整数提升到一个最小的上下文中,在这个上下文中映射不适用于偶数。换句话说,你可以用你的构图映射,但一旦你击中偶数,节目就结束了。
这看起来有点像 Maybe:
class noEvens {
constructor(x) {
this._val = x;
}
static of(x) {
return new noEvens(x);
}
isEven() {
return this._val % 2 === 0;
}
map(projF) {
return this.isEven() ? noEvens.of(null) : noEvens.of(projF(this._val));
}
}
但很明显,在某些情况下,这不会与应用于普通 JS 类别中的整数的组合交换。考虑一个简单地将整数加一的投影函数。
如果我将一个偶数提升到这个 noEvens 上下文中,然后添加一个,它将给我一个 null 的 noEvens。但是,如果我先将一个加到一个偶数上,然后提升结果,它会导致奇数的 noEvens。
根据我的理解,这两种途径都应该根据函子定律进行交换。他们显然没有,因为通过每个上下文的相同映射在被提升后不会产生相同的结果 "noEvens.of(value)"。
所以我想我的问题是,这是否意味着这不是函子?这种情况(按类型或其他)是什么让它表现得很奇怪?
我想我只是感到困惑,因为似乎所有 "noEvens" 所做的就是将值提升到一个新的上下文(子类别,无论什么)中,其中偶数不存在,但它是很明显,某些路径不会通勤。
我发现 "lifting a value" 进入新映射上下文的想法非常直观,它为您提供了很多处理条件的机会,而无需实施大量冗余代码。但我不想假装我遵守 "functor laws".
的一些正式系统我在分析这种情况时遗漏了什么类型系统和函子定律?
除了我的评论...
您可能会注意到您的 almost-functor class 也不满足恒等式。
const id = x => x;
new noEvens(2).map(id) // != new noEvens(2)
我的第一个想法是错误是允许首先构造一个包含偶数的 noEvens 对象。如果 isEven 检查改为在构造函数中完成,那么您可以满足 ID 法则。
class noEvens {
constructor(x) {
if (x % 2 === 0) {
this._val = null;
} else {
this._val = x;
}
}
static of(x) {
return new noEvens(x);
}
map(projF) {
if (this._val === null) {
return noEvens.of(null);
}
return noEvens.of(projF(this._val));
}
}
const id = x => x;
new noEvens(1).map(id)._val // ok: 1
new noEvens(2).map(id)._val // ok: null
但是,结果发现这个解还是不满足组合律
const plusOne = x => x + 1;
// fmap f . fmap g == fmap (f . g) ?
new noEvens(1).map(plusOne).map(plusOne)._val // null
new noEvens(1).map(x => plusOne(plusOne(x)))._val // 3
所以最终我认为致命的缺陷是 noEvens 限制了它可以保存的数据类型。正如 Bergi 所说,"A usual functor would be capable of containing any arbitrary data." 所以 noEvens 作为一个概念的核心,不可能是一个遵守合成法则的函子。