Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Type refinements #162

Closed
wants to merge 20 commits into from
Closed
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
27 changes: 12 additions & 15 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -797,17 +797,16 @@
//. the type comprising every [`String`][] value except `''`.
//.
//. The given type must satisfy the [Monoid][] and [Setoid][] specifications.
var NonEmpty = UnaryType(
'sanctuary-def/NonEmpty',
functionUrl('NonEmpty'),
Any,
function(x) {
function NonEmpty(parent) {
function test(x) {
return Z.Monoid.test(x) &&
Z.Setoid.test(x) &&
!Z.equals(x, Z.empty(x.constructor));
},
function(monoid) { return [monoid]; }
);
}
function extract(monoid) { return [monoid]; }
var t = UnaryTypeWithUrl('sanctuary-def/NonEmpty', parent, test, extract);
Copy link
Member Author

Choose a reason for hiding this comment

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

Using parent as an actual parent type, like I'm doing here, seems like a good idea. Now NonEmpty String will (correctly) be a "refinement" of String.

However, as indicated by the failing unit test, it causes a value of [1] for $NonEmpty($Array($String)), for example, to generate this error message:

NonEmpty (Array String)
         ^^^^^^^^^^^^^^
               1

1) 1 :: Number

The value at position 1 is not a member of ‘Array String’. 

Where we would want:

NonEmpty (Array String)
                ^^^^^^
                  1

1) 1 :: Number

The value at position 1 is not a member of ‘String’.

I'm not sure why this happens. Replacing parent with Any solves the issue.

Copy link
Member

Choose a reason for hiding this comment

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

The problem is that

$.NonEmpty($.Array($.String)).validate([1, 2, 3]).value.propPath

evaluates to ['$1'] rather than ['$1', '$1'] because we first validate the parent type and, if validation fails, make no adjustment to propPath to account for the extra level of nesting introduced by $.NonEmpty.

Copy link
Member Author

Choose a reason for hiding this comment

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

That seems like an issue that will bite us in some other way, one day. I don't yet see a solution.

Copy link
Member Author

@Avaq Avaq Jul 10, 2017

Choose a reason for hiding this comment

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

In it's general form, I think the issue could be described as: When the validation of a refined type fails on its parent type, the reported propPath may not correctly correspond to the location of the value as it exists in the refined type

We even encounter this issue if we refine a record type to be "strict", as has always been the suggestion:

const User = $.RecordType({ name: $.String, password: $.String });
const StrictUser = $.NullaryType('', '', User, x => Object.keys(x).length ===
                                                    User.keys.length);

const validation = StrictUser.validate({ name: 'bob', password: 1 });
// propPath = [ 'password' ]

StrictUser.types[validation.value.propPath[0]] // => undefined
// the above expression should never return undefined, as the propPath must
// correspond to the types mapping. It does because our propPath is actually
// corresponding to another types mapping!

Copy link
Member Author

@Avaq Avaq Jul 10, 2017

Choose a reason for hiding this comment

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

The same problem, biting us somewhere else:

const Point = $.RecordType($.Any, { x: $.Number, y: $.Number });
const Point3D = $.RecordType(Point, { z: $.Number });

const validation = Point3D.validate({ x: 1, y: 'two', z: 3 });
// propPath = [ 'y' ]

Point3D.types[validation.value.propPath[0]] // => undefined

Copy link
Member Author

Choose a reason for hiding this comment

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

This is particularly problematic, because the property names described by propPath might exist in both the parent type and the refinement, and validate does not tell us which it was.

return t(parent);
}

//# Nullable :: Type -> Type
//.
Expand Down Expand Up @@ -839,13 +838,11 @@
//. Constructor for strict record types.
//. `$.Strict($.Record($.Any, {name: $.String}))`, for example, is the type
//. comprising every object with exactly one field, `name`, of type `String`.
function Strict(t) {
return NullaryType('sanctuary-def/Strict',
functionUrl('Strict'),
t,
function test(x) {
return t.keys.length === keys(x).length;
});
function Strict(parent) {
function test(x) { return parent.keys.length === keys(x).length; }
function extract(record) { return [record]; }
var t = UnaryTypeWithUrl('sanctuary-def/Strict', parent, test, extract);
return t(parent);
}

//# StrMap :: Type -> Type
Expand Down