Understanding functor laws: is this a functor?

断了今生、忘了曾经 提交于 2021-02-08 08:48:18

问题


The following code is written in javascript.

This question involves an attempt to dive into some category theory, maybe a haskeller or someone more familiar with the mathematical aspects of this question can help me out?

I'm trying to wrap my head around the idea that a functor is a mapping between categories that preserves structure. More specifically - according to my understanding - a functor in a programming language is an endofunctor. By this it is meant that functors in programming languages are morphisms that map types and functions to a sub-category within the broader category of types and functions as defined generally within the programming language.

According to my knowledge, functors (or endofunctors) must also adhere to certain laws that afford preservation of structure via the facilitation of composition and identity.

I'm finding it almost impossible to create a functor that I feel preserves structure, and adheres to the functor laws. This is compounded by the fact that I've only really programmed seriously in javascript, so type theory is something I've never really thought about (JS is untyped after all).

I made this simple example that lifts integers into a minimal context where mappings won't work on even numbers. In other words, you can map away with your composition but as soon as you hit an even number the show is over.

This looks sort of like a 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));
  }
}

But it's evident that this won't commute with compositions applied to integers in the normal JS category under certain situations. Consider a projection function that simply adds one to an integer.

If I lift an even number into this noEvens context and then add one it will give me a noEvens of null. But, if I first add one to an even number, then lift the result, it will result in a noEvens of an odd Number.

According to my understanding, both of these pathways should commute under functor laws. They clearly don't because the same mappings through each context don't result in the same resultant "noEvens.of(value)" after being lifted.

So I guess my question is, does this mean that this is not a functor? What is it about this type of situation (type-wise or whatever) that makes it act strangely?

I guess I'm just confused because it seems to be that all "noEvens" does is lift values into a new context (sub-category, whatever) where even numbers don't exist, but it is obvious that certain pathways won't commute.

I find the idea of "lifting a value" into a new mapping context quite intuitive, and it affords you a lot of opportunities to deal with conditions without having to implement tonnes of redundant code. But I don't want to do so in the false pretense that I'm adhering to some formalized system of "functor laws".

What is it about type-systems and functor laws that I'm missing in my analysis of this situation?


回答1:


In addition to my comment...

You may notice that your almost-functor class also doesn't satisfy the identity law.

const id = x => x;

new noEvens(2).map(id) // != new noEvens(2)

My first thought was that the mistake was allowing a noEvens object containing an even number to be constructed in the first place. If the isEven check was instead done in the constructor, then you could satisfy the ID law.

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

But, it turns out this solution still doesn't satisfy the composition law.

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

So ultimately I think the fatal flaw is that noEvens limits what kind of data it can hold. As Bergi also said, "A usual functor would be capable of containing any arbitrary data." So noEvens, at its core, as a concept, cannot be a functor that obeys the composition law.



来源:https://stackoverflow.com/questions/48958881/understanding-functor-laws-is-this-a-functor

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!