Skip to content

Conversation

kwijibo
Copy link

@kwijibo kwijibo commented Mar 16, 2016

addresses problem mentioned in #32

kwijibo added 2 commits March 16, 2016 10:25
2. Don't create record constructors if fields isn't a dictionary
@davidchambers
Copy link
Contributor

@paldepind, I urge you to reconsider supporting instanceof at all. It has caused many problems for both Ramda and Sanctuary. See sanctuary-js/sanctuary#100, for example.

@kwijibo
Copy link
Author

kwijibo commented Mar 16, 2016

@davidchambers to be clear, neither the existing master branch, nor this PR use instanceof (except in the tests admittedly). What this PR does is make the constructor create objects using the right prototype chain, so that given

const Animal = Type({Walrus: []})
const paul = Animal.Walrus()

both

R.is(Animal, paul)

and

R.is(Animal.Walrus, paul)

would return true.

@kwijibo
Copy link
Author

kwijibo commented Mar 16, 2016

@davidchambers Your description of instanceof in sanctuary-js/sanctuary#100 is interesting, but I don't find the behaviour of instanceof completely unreasonable. If you do an a instanceof b check, it's probably because you want to rely on a having a particular structure or behaviour that b guarantees. If a was actually just created by something with the same name/@@type as b, it might not behave in the required way.

@davidchambers
Copy link
Contributor

What this PR does is make the constructor create objects using the right prototype chain, so that [R.is may be used on the values].

instanceof is fragile, as is any function which makes use of it (such as R.is). Consider this example:

$ cat index.js
const Foo = require('foo');
const isFoo = require('is-foo');

console.log(isFoo(new Foo()));

$ cat node_modules/foo/index.js
module.exports = function Foo() {};

$ cat node_modules/is-foo/index.js
const Foo = require('foo');

module.exports = x => x instanceof Foo;

$ cat node_modules/is-foo/node_modules/foo/index.js
module.exports = function Foo() {};

$ node index.js
false

Even though the two foo modules contain the same bytes, they are not identical in memory. This is the module-level equivalent of identity equality. In functional programming we should be operating at a higher level of abstraction: equivalence rather than identity.

@kwijibo
Copy link
Author

kwijibo commented Mar 20, 2016

@davidchambers it sounds like what you object to fundamentally, is javascript object equality?
This has the same behaviour for the same reason I think (without using instanceof):

const isFoo = (function(){
 function Foo(){}
 return x => Foo.prototype.isPrototypeOf(x)
})()
function Foo(){}
const foo = new Foo()
isFoo(foo) //false

I don't find this surprising; it's consistent with javascript semantics (as I understand them). eg:

function isFoo(x){  var Foo = {}; return x==Foo }
var Foo = {}
isFoo(Foo) //false

});
it('constructor can be used with instanceof', function() {
var Point = Type({Point: [isNumber, isNumber]});
assert.equal(Point.Point(4,2) instanceof Point.Point, true);
Copy link
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why should Point.Point be an instance of the constructor for a Point? This doesn't seem right to me. Point represents a type. But Point.Point is just a function that creates a value of the type.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I agree!

Instead, we should create one predicate per data constructor. For example:

//    Maybe :: Type
const Maybe = Type({Nothing: [], Just: [R.T]});

//    Maybe.isNothing :: Maybe a -> Boolean
//    Maybe.isJust :: Maybe a -> Boolean

Maybe.isNothing(Maybe.Nothing());   // => true
Maybe.isNothing(Maybe.Just(42));    // => false
Maybe.isJust(Maybe.Nothing());      // => false
Maybe.isJust(Maybe.Just(42));       // => true

@paldepind
Copy link
Owner

I agree with @davidchambers that instanceof is problematic. union-type currently uses isPrototypeOf which I think suffer from all the same problems.

@davidchambers
Copy link
Contributor

I don't find this surprising; it's consistent with javascript semantics […].

Consistent, yes; helpful, no.

TypeError: ‘fromMaybe’ requires a value of type Maybe as its second argument; received Just(42)

Every module which exports functions which take or return values of the Maybe type must also export the type itself (even if it's defined by a third-party library).

const Maybe = require('maybe-type');
const quux = require('quux');

//    blah :: Maybe Number
const blah = quux.parseBlah('…');

//  Doesn't work, because `toArray` uses `instanceof` check.
Maybe.toArray(blah);

//  We must use `quux.Maybe.toArray` instead.
quux.Maybe.toArray(blah);

Furthermore, we may want to operate on multiple values of type Maybe a at once. What would we do if we had two such values created by "different" modules in memory? We might end up defining this:

//    foreignMaybeToMaybe :: ForeignMaybe a -> Maybe a
const foreignMaybeToMaybe = maybe =>
  maybe.isJust ? Maybe.Just(maybe.value) : Maybe.Nothing();

Let's avoid this complexity if possible. Let's provide a '@@type' property and avoid instanceof entirely.

@jgoux
Copy link

jgoux commented Mar 29, 2016

The @@type property would be a great solution! 👍

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants