Open thoward opened 11 years ago
@thoward thanks for taking the time to put this together! So the parser side of me likes where you're going with the pattern matching syntax in example 3, but I have to wonder 1) would this be clear to JavaScript programmers? I suppose the whitespace isn't significant. In jsig as proposed, the type indicated in example 3 could be written names: ({join: (Value, Value) => Value} | {toLowerCase: () => String})?
In long form, this reads:
a value called 'names'
which either has a method called "join",
which takes two parameters of any value and returns a value,
or a method called "toLowerCase" which takes no parameters and returns a string
There are a couple of things I notice that are emphasized by the different notations. jsig makes the return type explicit. Im not sure why it would make sense to specify the arity of a function without type hints. Jsig is intended to communicate to humans, not a method dispatcher. Do you have an example where it would be useful, to know the arity of a function you're consuming, or a callback you're expected to supply, without knowing anything further about the parameters?
I notice that this notation does a good job of being maximally specific, that is, indicating exactly which methods are actually necessary. This is good for ensuring flexibility and polymorphism (Liskov substitution principle). However, these type annotations are for people, not a type checker. Further, since JavaScript does not have classical inheritance, it is redundant to specify that An Array type must have a join method. Unless someone's deleting it from Array.prototype, the principle of least surprise would suggest referring to the default builtin interfaces. In the case where builtins are being augmented (like in mootools), the additions can be specified in addition to the builtins, eg Array&{contains: (Value) => Boolean}
. For communications purposes, however, it will often suffice to just specify String or Array, rather than an object with a join method
or an object with a toLowerCase method
.
From my perspective, types may not be the best way to specify things. I've shown a few examples below, of code snippets from the description, using an alternate syntax.
Example 1
Param
array
must implement methodreduce
, with arity of 2.Since the above method doesn't touch the other objects there's no need to specify them. It's up to the implementation of
reduce
to indicate requirements. This could vary by the implementation ofreduce
.If something is required, it begins with
!
... In this case, you must pass a non-null instance ofarray
, and it must have thereduce
method. The other params, we have no insight to, in the context of this method.Example 2
Same with this example,
res
must be non-null, andend
must exist on it, and have an arity of 1 (eg, takes a single param).Example 3
Here, our parameter
names
is not actually required. It's possible thatnames
is null, which means,!Array.isArray(names)
will returnfalse
, and the next lineconsole.log('hey' + names)
will be fine with a null value. Supposing!Array.isArray(names)
returnstrue
, thenjoin
will be called. Since we don't know if join will be called, we only indicated that it may be called using?join(_,_)
.A more powerful way to do this might be:
Example 4
Indicating again, that a null value for
names
is ok, but if it is of typeArray
(types are indicated w/ colon prefix), then it must implementjoin(_,_)
.Consider this logic:
Example 5
Here we specify that
names
must be implemented, and if it's anArray
then it must implementjoin(_,_)
, but otherwise, regardless of type, it must implementtoLowerCase()
.Example 6
Here, a null case is allowed, and so,
toLowerCase()
may not be called and that is indicated by?
prefix for both the param and the method declaration.