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

add type argument to S.get, S.gets, and S.pluck #64

Merged
merged 1 commit into from
Aug 24, 2015
Merged
Show file tree
Hide file tree
Changes from all commits
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
126 changes: 84 additions & 42 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,30 @@
//. (for any type `a`) with an argument of type `a -> b` (for any type `b`),
//. it returns a value of type `Maybe b`._
//.
//. ### Accessible pseudotype
//.
//. What is the type of values which support property access? In other words,
//. what is the type of which every value except `null` and `undefined` is a
//. member? Object is close, but `Object.create(null)` produces a value which
//. supports property access but which is not a member of the Object type.
//.
//. Sanctuary uses the Accessible pseudotype to represent the set of values
//. which support property access.
//.
//. ### Type representatives
//.
//. What is the type of `Number`? One answer is `a -> Number`, since it's a
//. function which takes an argument of any type and returns a Number value.
//. When provided as the first argument to [`is`](#is), though, `Number` is
//. really the value-level representative of the Number type.
//.
//. Sanctuary uses the TypeRep pseudotype to describe type representatives.
//. For example:
//.
//. Number :: TypeRep Number
//.
//. `Number` is the sole inhabitant of the TypeRep Number type.
//.
Copy link
Member Author

Choose a reason for hiding this comment

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

Does this convey the idea, @tel? Are there any wording changes you think would make this section clearer?

Copy link

Choose a reason for hiding this comment

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

That sounds great! Two minor points alone

  • "... Number is really the..." may be better said as "Number acts as the..."
  • TypeRep can be called a "type function" or "type constructor" if you like instead of invoking the term "pseudotype". Taken together, TypeRep might also be called a family of types or a fiber bundle... though that last term is quite rare!

👍

Copy link
Member Author

Choose a reason for hiding this comment

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

Thank you for your feedback. :)

"... Number is really the..." may be better said as "Number acts as the..."

I was about to :+1: when I realized that if a representative is one who acts on behalf of another, acts as the representative of X can be expanded to acts as the one who acts on behalf of X. Perhaps I'm overthinking things. :)

TypeRep can be called a "type function" or "type constructor" if you like instead of invoking the term "pseudotype".

Type constructor sounds good to me. I used the term "pseudotype" in #62:

The Int pseudotype represents integers in the range [-2^31 .. 2^31). It is a pseudotype because each Int is represented by a Number value. Sanctuary's run-time type checking asserts that a valid Number value is provided wherever an Int value is required.

I wanted a term to describe a thing which is a type in the sense of a set of values but not in the sense of typeof or instanceof. I reused the term in this pull request to describe Accessible, and later TypeRep.

Describing TypeRep as a type constructor makes its role clearer. We can use the following subheadings:

### Accessible pseudotype

### TypeRep type constructor

Copy link
Member Author

Choose a reason for hiding this comment

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

I decided to use ### Type representatives as the subheading instead.

//. ## API

;(function() {
Expand Down Expand Up @@ -101,13 +125,13 @@
});

var Accessible = /* istanbul ignore next */ function Accessible() {};
var Type = /* istanbul ignore next */ function Type() {};
var TypeRep = /* istanbul ignore next */ function TypeRep() {};
var a = {name: 'a'};
var b = {name: 'b'};
var c = {name: 'c'};

var _is = function(type, x) {
return x != null && Object(x) instanceof type;
return x != null && (type === Accessible || Object(x) instanceof type);
};

var arity = function(n, f) {
Expand All @@ -119,7 +143,7 @@
}
};

// curry :: (String, [Type], [*], Function) -> Function
// curry :: (String, [TypeRep], [*], Function) -> Function
var curry = function(name, types, _values, f) {
return arity(R.filter(placeholder, _values).length, function() {
var values = _values; // Locally scoped variable to update.
Expand Down Expand Up @@ -161,7 +185,7 @@
[paramIndex, name]
));
}
} else if (!_is(type === Type ? Function : type, arg)) {
} else if (!_is(type === TypeRep ? Function : type, arg)) {
throw new TypeError(format(
'{quote} requires a value of type {type} as its {ord} argument; ' +
'received {repr}',
Expand Down Expand Up @@ -210,11 +234,11 @@

//. ### Classify

//# is :: Type -> a -> Boolean
//# is :: TypeRep a -> b -> Boolean
//.
//. Takes a type and a value of any type and returns `true` if the given
//. value is of the specified type (either directly or via the prototype
//. chain); `false` otherwise.
//. Takes a [type representative](#type-representatives) and a value of
//. any type and returns `true` if the given value is of the specified
//. type (either directly or via the prototype chain); `false` otherwise.
//.
//. Boolean, number, string, and symbol [primitives][] are promoted to
//. their object equivalents. `42`, for example, is considered a Number
Expand All @@ -231,7 +255,7 @@
//. > S.is(String, 42)
//. false
//. ```
var is = S.is = def('is', [Type, a], _is);
var is = S.is = def('is', [TypeRep, a], _is);

//. ### Combinator

Expand All @@ -253,7 +277,7 @@

//. ### Maybe type

//# Maybe :: Type
//# Maybe :: TypeRep Maybe
//.
//. The Maybe type represents optional values: a value of type `Maybe a` is
//. either a Just whose value is of type `a` or a Nothing (with no value).
Expand Down Expand Up @@ -480,7 +504,7 @@
//. "Just([1, 2, 3])"
//. ```

//# Maybe#type :: Type
//# Maybe#type :: TypeRep Maybe
//.
//. A reference to the Maybe type. Useful for determining whether two
//. values such as `S.Nothing()` and `S.Just(42)` are of the same type.
Expand Down Expand Up @@ -648,7 +672,7 @@

//. ### Either type

//# Either :: Type
//# Either :: TypeRep Either
//.
//. The Either type represents values with two possibilities: a value of type
//. `Either a b` is either a Left whose value is of type `a` or a Right whose
Expand Down Expand Up @@ -822,7 +846,7 @@
//. "Right([1, 2, 3])"
//. ```

//# Either#type :: Type
//# Either#type :: TypeRep Either
//.
//. A reference to the Either type. Useful for determining whether two
//. values such as `S.Left('Cannot divide by zero')` and `S.Right(42)`
Expand Down Expand Up @@ -1308,59 +1332,77 @@
//. ```
S.lastIndexOf = sanctifyIndexOf('lastIndexOf');

//# pluck :: String -> [{String: *}] -> [Maybe *]
//# pluck :: TypeRep a -> String -> [Accessible] -> [Maybe a]
//.
//. Takes a list of objects and plucks the value of the specified key
//. for each object in the list. Returns the value wrapped in a Just
//. if an object has the key and a Nothing if it does not.
//. Takes a [type representative](#type-representatives), a property name,
//. and a list of objects and returns a list of equal length. Each element
//. of the output list is Just the value of the specified property of the
//. corresponding object if the value is of the specified type (according
//. to [`is`](#is)); Nothing otherwise.
//.
//. ```javascript
//. > S.pluck('a', [{a: 1, b: 2}, {a: 4, b: 5}, {b: 3, c: 7}])
//. [Just(1), Just(4), Nothing()]
//. See also [`get`](#get).
//.
//. > S.pluck('x', [{x: 1}, {x: 2}, {x: undefined}])
//. [Just(1), Just(2), Just(undefined)]
//. ```javascript
//. > S.pluck(Number, 'x', [{x: 1}, {x: 2}, {x: '3'}, {x: null}, {}])
//. [Just(1), Just(2), Nothing(), Nothing(), Nothing()]
//. ```
S.pluck = def('pluck', [String, Accessible], function(key, xs) {
return R.map(get(key), xs);
S.pluck =
def('pluck', [TypeRep, String, Accessible], function(type, key, xs) {
return R.map(get(type, key), xs);
});

//. ### Object

//# get :: String -> Object -> Maybe *
//# get :: TypeRep a -> String -> Accessible -> Maybe a
//.
//. Takes a property name and an object and returns Just the value of
//. the specified property of the object if the object has such an own
//. property; Nothing otherwise.
//. Takes a [type representative](#type-representatives), a property
//. name, and an object and returns Just the value of the specified object
//. property if it is of the specified type (according to [`is`](#is));
//. Nothing otherwise.
//.
//. The `Object` type representative may be used as a catch-all since most
//. values have `Object.prototype` in their prototype chains.
//.
//. See also [`gets`](#gets).
//.
//. ```javascript
//. > S.get('x', {x: 1, y: 2})
//. > S.get(Number, 'x', {x: 1, y: 2})
//. Just(1)
//.
//. > S.get('toString', {x: 1, y: 2})
//. > S.get(Number, 'x', {x: '1', y: '2'})
//. Nothing()
//.
//. > S.get(Number, 'x', {})
//. Nothing()
//. ```
var get = S.get =
def('get', [String, Accessible],
R.ifElse(R.has, R.compose(Just, R.prop), Nothing));
def('get', [TypeRep, String, Accessible], function(type, key, obj) {
return filter(is(type), Just(obj[key]));
});

//# gets :: [String] -> Object -> Maybe *
//# gets :: TypeRep a -> [String] -> Accessible -> Maybe a
//.
//. Takes a [type representative](#type-representatives), a list of property
//. names, and an object and returns Just the value at the path specified by
//. the list of property names if such a path exists and the value is of the
//. specified type; Nothing otherwise.
//.
//. Takes a list of property names and an object and returns Just the
//. value at the path specified by the list of property names if such
//. a path exists; Nothing otherwise.
//. See also [`get`](#get).
//.
//. ```javascript
//. > S.gets(['a', 'b', 'c'], {a: {b: {c: 42}}})
//. > S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: 42}}})
//. Just(42)
//.
//. > S.gets(['a', 'b', 'c'], {})
//. > S.gets(Number, ['a', 'b', 'c'], {a: {b: {c: '42'}}})
//. Nothing()
//.
//. > S.gets(Number, ['a', 'b', 'c'], {})
//. Nothing()
//. ```
S.gets = def('gets', [Accessible, Accessible], function(keys, obj) {
return R.reduce(function(acc, key) {
return R.chain(get(key), acc);
}, Just(obj), keys);
S.gets =
def('gets', [TypeRep, Accessible, Accessible], function(type, keys, obj) {
var f = function(m, k) { return R.chain(get(Accessible, k), m); };
return filter(is(type), R.reduce(f, Just(obj), keys));
});

//. ### Parse
Expand Down
Loading