Sanctuary.Js Type Error with Identity Functor

391 views Asked by At

I am playing around following Bartosz Milewski category theory lessons on youtube. He describes Const and Identity functors as the "base" functors can be derived from (probably a liberal paraphrasing on my part).

My problem, having implemented ES6+ / fantasy-land (not important) version of the functor, appears once I start to integrate with the Sanctuary libary for map and pipe.

The implementation is pretty simple

const {map: flMap, extract } = require('fantasy-land');

const getInstance = (self, constructor) =>
    (self instanceof constructor) ?
        self :
        Object.create(constructor.prototype) ;

const Identity = function(x){
    const self = getInstance(this, Identity)

    self[flMap] = f => Identity(f(x))
    self[extract] = () => x

    return Object.freeze(self)
}

Here is some simple usage (as I was also working ion deriving lenses)

// USAGE
const {map, pipe, curry} = require("sanctuary")

const extractFrom = x => x[extract]()

const setter = (f, x) => (pipe([
    Identity,
    map(f),
    extractFrom
])(x))

const double = x => x + x

console.log(Identity(35)) //=> 35
console.log(map(double, Identity(35))) // ERROR Should be Identity(70)
console.log(setter(double, 35)) // ERROR Should be: 70
TypeError: Type-variable constraint violation

map :: Functor f => (a -> b) -> f a -> f b
                     ^            ^
                     1            2

1)  35 :: Number, FiniteNumber, NonZeroFiniteNumber, Integer,
    NonNegativeInteger, ValidNumber

2)  () => x :: Function, (c -> d)
    f => Identity(f(x)) :: Function, (c -> d)

Since there is no type of which all the above values are members, the
type-variable constraint has been violated.

However the Const functor works a bit better (no f invoked in map)

const Const = function(x) {
    const self = getInstance(this, Const)

    self[map] = _ =>  Const(x)
    self[extract] = () => x

    return Object.freeze(self)
}

const getter = (f, x) => (pipe([
    Const,
    map(f),
    extractFrom
])(x))

console.log(getter(double, 35)) //=> 35

Further everything is "logically sound" as proven by removing the type checking

const {create, env} = require('sanctuary'); 
const {map, pipe, curry} = create({checkTypes: false, env: env});

or replacing sanctuary with ramda. SO it looks like some sort of type consistency problem with Identity map function.

Question is how do I get all these parts to play together in a type happy sort of way.

1

There are 1 answers

4
davidchambers On BEST ANSWER

You'll need to define a type constructor for your type (IdentityType :: Type -> Type), and include IdentityType ($.Unknown) in your Sanctuary environment as described in the S.create documentation. Specifically, you'll need something like this:

//    IdentityType :: Type -> Type
const IdentityType = $.UnaryType
  ('my-package/Identity')
  ('http://example.com/my-package#Identity')
  (x => type (x) === Identity['@@type'])
  (identity => [Z.extract (identity)]);

const S = create ({
  checkTypes: process.env.NODE_ENV !== 'production',
  env: env.concat ([IdentityType ($.Unknown)]),
});

In the snippet above, $ refers to sanctuary-def, Z refers to sanctuary-type-classes, and type refers to sanctuary-type-identifiers.