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

Conversation

davidchambers
Copy link
Member

The fact that S.get returns a value of type Maybe * has bothered me for some time. I find myself filtering the result to ensure I have a value of the expected type. For example:

R.pipe(...,
       S.get('x'),
       R.filter(R.is(Number)),
       ...)

This pull request adds a type specifier to S.get, S.gets, and S.pluck. The above will become:

R.pipe(...,
       S.get(Number, 'x'),
       ...)

The current loose behaviour can be approximated by S.get(Object). It's no longer possible to get null/undefined values. If this is important we could export an Any pseudotype, where Any contains every JavaScript value. Unless there's a use case, I'd prefer not to export and document S.Any.

These changes build upon the foundation laid by #63. I've included a section explaining the Accessible pseudotype, as it's now used in type signatures.

One question I have is how to link Type and a in a signature such as:

get :: Type -> String -> Accessible -> Maybe a

Any suggestions?

@pranavvishnu
Copy link
Contributor

This should be really useful @davidchambers!

I think saying Typeof a is a very readable way to link Type and a, but there's possibly a better convention out there somewhere.

//. value `true` is also considered "true". Other types must provide
//. if the first is true; the first value otherwise. An array is
//. considered true if its length is greater than zero. The Boolean
//. value `true` is also considered true. Other types must provide
Copy link
Contributor

Choose a reason for hiding this comment

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

Should the fancy quotation marks be a part of this PR?

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 wanted to use curly quotes around “plucking”, and wanted to be consistent. I'll switch to curly quotes in a separate pull request then rebase this branch. :)

@benperez
Copy link
Contributor

One question I have is how to link Type and a in a signature

You could use the syntax for a class constraint.

get :: Type a => Type -> String -> Accessible -> Maybe a

@davidchambers
Copy link
Member Author

You could use the syntax for a class constraint.

get :: Type a => Type -> String -> Accessible -> Maybe a

Since a is already a type variable, I'm not sure what information the Type a constraint adds. Also, I don't see how this relates the return type to the type of the first argument.

@joneshf
Copy link
Member

joneshf commented Jul 18, 2015

I think you have to be the type checker in this case. Look at where Type flows through your program, and make the decision based on that.

It is sent to is in get

In is it is compared to Accessible. So it should probably have a type like: () -> Accessible.

It also is used as the RHS of instanceof on the same line. So, it must be a constructor. Since what it is checked against is a value (the a) it should have a type like: () -> a.

Combine the two types and you end up with something like: () -> (Accessible | a), where | represents a union.

So putting it all together you get something like:

get :: (() -> (Accessible | a)) -> String -> Accessible -> Maybe a

@davidchambers
Copy link
Member Author

@joneshf, I don't understand how you arrived at () in () -> (Accessible | a). Are you using () to indicate zero arguments? Take String as an example of a type:

> String(42)
'42'
> String(null)
'null'

This suggests we have something like String :: * -> String. But the "constructor" can be given no arguments (in which case it returns ''). Then we have RegExp which accepts multiple arguments. So perhaps we should replace Type with *... -> a. But there are an infinite number of functions which return a value of type a for any given a, not just the a constructor.

How about this?

get :: A -> String -> Accessible -> Maybe a

This would mean we're treating single-letter identifiers—rather than lower-case identifiers—as type variables. This is a deviation from Haskell, but the correspondence between A and a is quite nice.

@tel
Copy link

tel commented Jul 21, 2015

One thing that might work is

TypeRep a -> String -> Accessible -> Maybe a

This is more exact since the values like Number are value-level representatives of types, not types themselves (this is confusing nomenclature in Javascript, since the word "type" is so overloaded, but it is certainly accurate).

We can imagine an indexed family of types of kind TypeRep : * -> * where for each type a, TypeRep a has exactly one value—a singleton. This value represents the types at the value level.

@davidchambers
Copy link
Member Author

@tel, are you suggesting that Number is the sole value of the TypeRep Number type? I want to make sure I've understood your comment. :)

@tel
Copy link

tel commented Jul 21, 2015

Yeah, exactly! We cannot instantiate this type directly in Haskell, but it'd look a bit like this

data TypeRep a where
  Number :: TypeRep Number
  Array :: TypeRep a -> TypeRep (Array a)
  ...

The difficulty is that in Haskell we need to ultimately close the type (end it after finitely many variants have been specified) yet we're allowed to continue specifying new types afterward. Worse, there's no way to get values at TypeRep (TypeRep X), e.g.

But ignoring those issues that's the exact idea.

@davidchambers
Copy link
Member Author

Okay. I like the idea. Do these changes seem right?

- is :: Type -> a -> Boolean
+ is :: Type a -> b -> Boolean

- Maybe :: Type
+ Maybe :: Type Maybe

- Maybe#type :: Type
+ Maybe#type :: Type Maybe

- Either :: Type
+ Either :: Type Either

- Either#type :: Type
+ Either#type :: Type Either

- pluck :: String -> [{String: *}] -> [Maybe *]
+ pluck :: Type a -> String -> [Accessible] -> [Maybe a]

- get :: String -> Object -> Maybe *
+ get :: Type a -> String -> Accessible -> Maybe a

- gets :: [String] -> Object -> Maybe *
+ gets :: Type a -> [String] -> Accessible -> Maybe a

Unless there's a compelling reason to use TypeRef rather than Type, I'd prefer to stick with the latter since we're already using it (S.Just(42).type, for example, evaluates to S.Maybe).

@tel
Copy link

tel commented Jul 21, 2015

I suppose those work, but using Type directly feels very confusing to me. Maybe :: Type Maybe at the value level certainly isn't a type after all! Generally that's looking good though.

@davidchambers
Copy link
Member Author

Yeah, TypeRef is much better. I'll update the type signatures but leave the type property alone for now. We can consider renaming it typeref or typeRef in a separate pull request.

@davidchambers davidchambers force-pushed the dc-property-access branch 3 times, most recently from 2156e2c to 8c0555b Compare July 23, 2015 06:18
//. 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.

@davidchambers
Copy link
Member Author

I've changed occurrences of Type in type signatures to TypeRep (not TypeRef as I mistakenly wrote in earlier comments).

Setting aside type signatures for a moment, do others agree with the API change in question? Do you find the rationale compelling, @svozza?

@svozza
Copy link
Member

svozza commented Jul 23, 2015

I definitely like this, the main issue I used to have with gets was that often there'd be a chance that the result would just be a wrapped null value, which meant I actually avoided using the function. Big +1 from me.

@davidchambers davidchambers force-pushed the dc-property-access branch 4 times, most recently from e7b2e3e to 1ff8bcb Compare August 24, 2015 11:06
davidchambers added a commit that referenced this pull request Aug 24, 2015
add type argument to S.get, S.gets, and S.pluck
@davidchambers davidchambers merged commit ad66b14 into master Aug 24, 2015
@davidchambers davidchambers deleted the dc-property-access branch August 24, 2015 11:17
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.

6 participants