ericelliott/rtype

Defining dynamic but consistent types

Opened this issue · 14 comments

The FooByUser object is a simple structure representing the amount of foo performed by each user, setting each property's key & value to the username & recorded foo respectively.

The collection of keys composing the object may vary between objects representing different periods. For example if new user3 records foo in April then the March and April objects will differ in keys : {user1: 100, user2: 3} vs {user1: 87, user2: 5, user3: 30 }.

From a code documentation perspective I want to define FooByUser as having a set of property keys of known origin & type (if such a word can be used), but which cannot be predetermined for each particular instance.

Could we have a syntax something like the following?

interface FooByUser {
  *<username>: Number
}
def username {
  desc:    "The username of a user",
  source: {type: "database", location: "users.id"} 
}

Where the <> demonstrate that that we are using a def rather than a static key and the * represents that there may be more than one of these keys.

I would generate the type definition of FooByUser using a script in your build process.

IMO we should reuse the ES6 computed property name syntax.

// let's assume Integer is defined

interface FooByUser {
  ['username' + Integer]: Number
}
// or using expression interpolation
interface FooByUser {
  [`username${Integer}`]: Number
}

Concerning desc and source it should be considered as another feature and—if it is still warranted—moved to a separate issue.

I'm aiming at getting a type definition which is non-specific about the number and name of keys at run time, so generation at build time will not work unfortunately.

I like the suggestion of using a computed property syntax for bringing this non-specificity to the name of the key. We would still need a way of expressing the potential multiplicity of the property definition if this is all seen as worthwhile.

I will raise a separate issue to discuss the benefits of the desc and source proposal.

@js-jslog Is this type only meant for documentation (no static or runtime type checking)?

The motivation for this question comes from wanting documentation for these types. In an ideal world we would have a solution open to all the type checking benefits too. I appreciate that this might create challenges there though..

Taking my question from #105

My issue is about values. Reading #97 it looks like the syntax for values is defined.

{
  String: Number
}

// or in my case
{String: RegExp}

Correct me if I'm wrong.

Let's list the prerequisites. A property name can be of 3 types:

  • identifier name
  • string literal
  • numeric literal (coerced into a string)

A property key value is either an ECMAScript String value or a Symbol value.

Which means we only have 2 possible types for the key: Symbol and String.
Now let's cover the simplest case: all properties of the object have the same type for both the key and the value.

Object( String: RegExp )
// equivalent to
{ String: RegExp }

We should be able to reuse Object.
cf #99 (comment)

What's the most common use case? The key being a string.
Which means we should cater for it by having a syntax which defaults to that type without having to specify it explicitly.

What should it be? Once we have answered that question we will have a proper basis so that we will be able to provide a generic solution to cover the case that interests the OP.

Look like #99 (and #97) is awaiting for the #80 to be closed. I believe #80 is settled. Can we close #80 as done, or is there some work on hold there yet?

@koresar trying to tackle everything at once is not my strategy.
cf #96 (comment)

If we could settle for micro enhancements backed by real use cases, that would be much preferable.
It's also easier for ppl to chime in without excessive prior knowledge; which is a good thing if we strive for accessibility.

@ericelliott might have a say on this though.

Sorry bringing an irrelevant topic.

It's also easier for ppl to chime in without excessive prior knowledge;

This is already impossible.
The square bracket are proposed to be used as: generics and computed properties.
Whereas in JS it is used for: arrays and computed properties.

So, people would need to adapt to the different syntax regardless. We either should declare and hold to our "different syntax path", or use existing JS practices and hold to "JS ecosystem syntax path".


BTW, it's a good idea to declare rtype's evolution directions. Currenly all we have is

  • Great for simple documentation.
  • Compiler-agnostic type notation - for use with ECMAScript standard tools.
  • Can embed in JS as strings, for example with rfx. Affords easy runtime reflection.
  • Standing on the shoulders of giants. Inspired by: ES6, TypeScript, Haskell, Flow, & React

It lacks something like:

  • Aim to provide low learning curve for JS devs. Or,
  • Aim to provide better syntax than any other language.

We either should declare and hold to our "different syntax path", or use existing JS practices and hold to "JS ecosystem syntax path".

Whenever we can we will provide something that is familiar.

Concerning the declared intents, I am not inclined to edit these: Eric is the one who knows what he envisioned originally. You can do a PR though.

Let's come back to the OP request. What about regular expressions?

interface FooByUser {
  /^user[1-9][0-9]*$/: Number
}

Since it's already allowed for the value it would make sense to allow it for the key as well.
If that's settled we just need a way to convey that is true for all properties of the object.

In #115 I propose:

interface Example {
   [[aka: String(/^element\-[1-118]$/)]]: String
 }

For convenience, should we have a shorthand?

// would be equivalent

interface Example {
   [[aka: /^element\-[1-118]$/]]: String
 }

Should the notion of sub-type be kept if we allow the shorthand? In that case we would end up with 4 types allowed for property names*: String, Symbol, Number and RegExp. The latter two would map to String.

// so instead of
interface Example {
  [[label: String(Number)]]: String
}

// we'd have
interface Example {
  [[label: Number]]: String
}

* not key

It sounds like what you really want is a generic collection, where we don't care about the names of the keys at all. You could implement such a collection that shares some convenience methods from Arrays using #120:

typeclass Foldable {
  reduce (this: Foldable[a], (accumulator: b, item: a) => b, initial?: a) => b
};

typeclass Semigroup {
  concat (this: Semigroup[a], ...args: [...Semigroup[Any]]) => Semigroup[Any]
};

typeclass Functor {
  map (this: Functor[a], a => b) => Functor[b]
};

typeclass Collection: Foldable & Semigroup & Functor;

interface User {
  name: String,
  number: Number
};

interface UserCollection: Collection[User] & {
  byName (this, name: String) => Number
}

Would that work for your usecase?