0

I am trying this from scratch learning about Contravariants and deeper knowledge of Sanctuary. The code "works" but again I don't have the types exactly right.

Here is the Contravariant

const {contramap: contramapFl, extract } = require('fantasy-land');
const getInstance = (self, constructor) =>
    (self instanceof constructor) ?
        self :
        Object.create(constructor.prototype) ;
// Contra a ~> g: a -> ?
const Contra = function(g){
    const self = getInstance(this, Contra)

    // :: F a ~> b -> a -> F b [ b -> a -> ? ]
    self[contramapFl] = f => Contra( x => g(f(x)) )
    self[extract] = g
    self['@@type'] =  'fs-javascript/contra'

    return Object.freeze(self)
}
// UPDATE adding type to constructor
Contra['@@type'] =  'fs-javascript/contra'

And my attempt to get the types right

const $ = require('sanctuary-def');
const type = require('sanctuary-type-identifiers');
const Z = require('sanctuary-type-classes') ;

const isContra = x => type (x) === 'fs-javascript/contra'

const ContraType = $.UnaryType(
    'fs-javascript/contra',
    'http://example.com/fs-javascript#Contra',
    isContra,
    x => [x[extract]])($.Unknown)

Then my test

const {create, env} = require('sanctuary');
const {contramap} = create({checkTypes: true, env: env.concat(ContraType) });

const isEven = Contra(x => x % 2 === 0) ;
console.log(Z.Contravariant.test(isEven)) // => true

const isLengthEvenContra = contramap(y => y.length, isEven)
const isStringLengthEven = isLengthEvenContra[extract]

console.log(isStringLengthEven("asw")) //=> ERROR
TypeError: Type-variable constraint violation

contramap :: Contravariant f => (b -> a) -> f a -> f b
                                              ^
                                              1

1)  "fs-javascript/contra" :: String
    f => Contra( x => g(f(x)) ) :: Function, (c -> d)
    x => x % 2 === 0 :: Function, (c -> d)

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

If I disable the type checking then it works as expected, so logically it appears to be stitched together properly. I defined my own version of contramap

const def = $.create({ checkTypes: true, env: $.env.concat(ContraType) });

const contramap2 =
    def('contramap2', {}, [$.Unknown, ContraType, ContraType],
        (f, x) => {
            const z = x[contramapFl](f)
            return z
        }
    )

I then rerun the test:

const isEven = Contra(x => x % 2 === 0) ;
console.log(Z.Contravariant.test(isEven)) // => true

const isLengthEvenContra = contramap2(y => y.length, isEven)
const isStringLengthEven = isLengthEvenContra[extract]

console.log(isStringLengthEven("asw")) //=> false

So withstanding the discussion as to whether the contravaiant functor is the best approach to this problem (learning exercise), the question is how, when defining my own implementation of a contravariant, can I use sanctuary's contramap function with the type checking enabled.


after updating by adding the code:

Contra['@@type'] =  'fs-javascript/contra'

changed the error to:

TypeError: Type-variable constraint violation

contramap :: Contravariant f => (b -> a) -> f a -> f b
                                      ^       ^
                                      1       2

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

2)  x => x % 2 === 0 :: Function, (c -> d)

Since there is no type of which all the above values are members, the type-variable constraint has been violated.
// Contra (Integer -> Boolean)
const isEven = Contra(x => x % 2 === 0) ;
// String -> Integer
const strLength = y => y.length
// I Think: Contra (String -> (Integer -> Boolean))
const isLengthEvenContra = contramap(strLength, isEven)
// (String -> (Integer -> Boolean))
const isStringLengthEven = isLengthEvenContra[extract]

My understanding of the contravariant functor was that it pre-composed the function within it, with the function passed in via contramap. So if the contravariant contained the function f and it is contramap with g it returns a new contravariant functor wrapping x = g(f(x)) Have i misunderstood this (too)

akaphenom
  • 6,728
  • 10
  • 59
  • 109
  • `@@type` must exist on the type representative rather than on the value itself. In other words, `x.constructor['@@type']` rather than `x['@@type']`. Could you provide the type signature of `Contra#fantasy-land/contramap`? It's not clear to me how it differs from [`Function#fantasy-land/contramap`](https://github.com/sanctuary-js/sanctuary-type-classes/blob/v9.0.0/index.js#L997-L1001). – davidchambers Jun 10 '18 at 06:33
  • @davidchambers thanks for the comments. I have added the signature to Contra constructor and it changed the error. I will update above. – akaphenom Jun 10 '18 at 19:30

2 Answers2

1

Here's the boilerplate for defining a custom type and including it in the Sanctuary environment:

'use strict';

const {create, env} = require ('sanctuary');
const $             = require ('sanctuary-def');
const type          = require ('sanctuary-type-identifiers');

//    FooType :: Type -> Type
const FooType = $.UnaryType
  ('my-package/Foo')
  ('https://my-package.org/Foo')
  (x => type (x) === 'my-package/Foo@1')
  (foo => []);

//    foo :: Foo
const foo = {
  'constructor': {'@@type': 'my-package/Foo@1'},
  'fantasy-land/contramap': function(f) {
    throw new Error ('Not implemented');
  },
};

const S = create ({
  checkTypes: true,
  env: env.concat ([FooType ($.Unknown)]),
});

S.I (foo);
// => foo

S.contramap (S.I) (foo);
// ! Error: Not implemented
davidchambers
  • 23,918
  • 16
  • 76
  • 105
0

You're composing functions. Sanctuary defines fantasy-land/map and fantasy-land/contramap for Function a b, so there's no need for a Contra wrapping type.

> S.map (S.even) (s => s.length) ('Sanctuary')
false

> S.contramap (s => s.length) (S.even) ('Sanctuary')
false
davidchambers
  • 23,918
  • 16
  • 76
  • 105
  • Thanks - however am I wrong in reading that that example doesn't make it obvious that the implementation is fantasy-land compliant (https://github.com/fantasyland/fantasy-land/tree/v3.5.0#contravariant): ```contramap :: Contravariant f => f a ~> (b -> a) -> f b``` . Doesn't that say contramap is a method on a contravariant object? – akaphenom Jun 11 '18 at 18:35
  • [sanctuary-type-classes](https://github.com/sanctuary-js/sanctuary-type-classes) provides implementations for built-in types, so one can pretend that `Function#fantasy-land/contramap` is defined when using Sanctuary. – davidchambers Jun 12 '18 at 11:32
  • meaning that so long as we are using a type defined within sanctuary it will be fantasy-land compliant. However (and am I correct) what I was trying to do (as an learning exercise, build my own type that was contravariant and use sanctuary's contramap) is not how have designed sanctuary to work (nor should you have). – akaphenom Jun 12 '18 at 13:20
  • Sanctuary *will* dispatch to the `fantasy-land/contramap` method of a custom type. My problem, though, is that I don't know of a type aside from `Function a b` and `Arrow a b` which can provide such a method (whereas I can name half a dozen types which can provide `fantasy-land/map`, for example). – davidchambers Jun 12 '18 at 18:19
  • I don't think I am appreciating the distinction. Let me dissect to demonstrate what I think I understand. The types ```Array```, ```Maybe``` etc all provide ```map``` and not ```contramap```. There are no "well known" types that implement ```contramap```. I think that is what you are saying and I would agree (at least to my current level of understanding). The question still stands if sanctuary will dispatch to ```contramap``` for a custom type - how do I convince Sanctuary that my type is capable? Many thanks for your help on this (and the libraries etc). – akaphenom Jun 13 '18 at 12:35
  • There *is* a well-known type that can support `contramap`: `Function a b` (often written `a -> b`). I've created an example so you can see how to define your own types and include them in the Sanctuary environment. It's too long for this context, so I'll post it as a separate answer. – davidchambers Jun 13 '18 at 17:06