jsigbiz / spec

JavaScript signature notation
130 stars 6 forks source link

General Feedback #2

Open ghost opened 11 years ago

ghost commented 11 years ago

Thank you so much for putting this spec together, @jden! I've seen various type annotation proposals for JS, and generally found them ill-suited to the language. Reading through the (now-defunct) ECMAScript 4 spec convinced me that attempting to integrate an existing type system would end in disaster. To that end, I'm excited about jsig because I think it's the first proposal that embraces JavaScript's dynamic nature.

My favorite part of jsig's design goals is that it's intended for humans: it's sufficiently descriptive for expressing intent ("a vocabulary for explicitly stating expectations"), but avoids the ceremonial boilerplate that plagues statically-typed languages. I also fully agree with your criticisms of JSDoc. Once you progress beyond built-in types and constructors, its utility becomes limited. JSDoc's @type annotations work well for "classical" (read: Java-inspired) patterns, but fall significantly short of meaningfully describing contemporary JS patterns.

Basic and Custom Types: I'm quite fond of using dot notation and the familiar object literal-style syntax (and array literal syntax, in the case of tuples) to describe objects and custom types. A brief clarification question: would it be accurate to think of custom types as interfaces, in the Java/C# sense?

Generic Types: Something about the use of angle brackets irked me at first (perhaps due to its association with Java generics, or, more likely, because I viscerally associate angle brackets with HTML), but I've gradually warmed to them. One question: given that there's an existing syntax for expressing multiple types (|), do you think there's value in reusing that instead of having the comma serve as a delimiter for multiple specific types?

// A possible alternative to `Generic<Type1, Type2>`.
Generic<Type1 | Type2>

Similarly, I presume that a custom or compound type can also be used as a specific type?

// An array of `Socket` objects.
Array<ReadStream & {write: Function, connect: Function}>

// ...Though that would probably be better expressed with a custom type.
Socket: ReadStream & {write: Function, connect: Function}
Array<Socket>

Compound Types: I love that this isn't restricted to just describing inheritance patterns: it works equally well for functional mix-ins and prototypal inheritance, to name just two contrasting philosophies. The choice of the ampersand is appropriate and reads well, in line with jsig's philosophy.

Return Types: The emphasis on return types makes sense (especially in the context of your "Thinking in Promises" post), but, given the popularity of callback-based patterns (where the return values aren't necessarily meaningful), using void to signify "no value" feels a bit verbose. Would it be ambiguous to simply omit the type in this case? (e.g., (param1, param2) =>). We already have the optional parameter syntax (?) for optional parameter values (or explicitly undefined values; I believe ES 6 treats them interchangeably for default parameters)—so I don't think there's a need to keep void around for that.

Parameters: The symmetry with the ES 6 fat arrow and rest parameter syntax is wonderful, and I also like the ability to specify optional parameter names. This might be too special-case for jsig, but what do you think about mirroring the spread syntax for an arbitrary number of parameters of different types? As an example, consider this definition for Function#bind:

bind(thisArg: Object, [...parameters]) => Function

Whew...this turned out to be quite a lengthy response. In short, I'm excited about jsig—both its syntax and its philosophy—and looking forward to hearing your feedback!

junosuarez commented 11 years ago

@kitcambridge thanks for your thoughtful and considered questions. see my replies inline below.

would it be accurate to think of custom types as interfaces, in the Java/C# sense?

Similar, but not in the sense of concrete implementation. Rather, it's structural typing all the way down. It's closer to the concept of an interface in TypeScript or Go. I'm not familiar with ActionScript, but I'm told it's similar. In just JavaScript terms, this is like the idea of "Array-like" being used to describe an object with a {length: Number} property and numeric indices. The important thing is that it's just describing the structural attributes of a Value.

given that there's an existing syntax for expressing multiple types (|), do you think there's value in reusing that instead of having the comma serve as a delimiter for multiple specific types?

// A possible alternative to Generic<Type1, Type2>. Generic<Type1 | Type2>

Generic<Type1|Type2> is valid under the proposal to mean "Generic is a type structure which varies by another type which may be either a Type1 or a Type2", while Generic2<Type1, Type2> means "Generic2 is a type structure which varies by two other types, one of which is a Type1 and the other of which is a Type2". (See also: covarianceMost common cases only vary by a single type (such as Callback<User> referenced in the spec). Given that jsig is meant to be read by humans, it would be worth discussing whether multivariate generics hinder or improve communication and comprehension. The most common usage I've seen in other language for multivariate generics is in describing functions, but in jsig we use ES6 lambda (arrow function) notation instead (for example, (Number, String) => Boolean could be described as Function<Number, String, Boolean>`).

Similarly, I presume that a custom or compound type can also be used as a specific type?

// An array of Socket objects. Array<ReadStream & {write: Function, connect: Function}>

// ...Though that would probably be better expressed with a custom type. Socket: ReadStream & {write: Function, connect: Function} Array

Exactly. The decision of which to use is stylistic and depends on the amount of context you want to establish in your documentation. A library, platform, or framework, for example, could adopt a few custom types which it uses in many places for the sake of clarity at the tradeoff of increased context that the reader has to hold in their head. On the other hand, if a single function you're writing has a particular constraint, it may be more clear to express it as a compound type. A programming analogy might be point-free style vs assignment, with similar readability tradeoffs.

Return Types: The emphasis on return types makes sense (especially in the context of your "Thinking in Promises" post), but, given the popularity of callback-based patterns (where the return values aren't necessarily meaningful), using void to signify "no value" feels a bit verbose. Would it be ambiguous to simply omit the type in this case? (e.g., (param1, param2) =>).

I've been turning this one over in my head as well. In my personal style I hate not returning (in fact, I like languages which return the value of the last expression in the function body by default, such as F# or CoffeeScript). However, for the purpose of JavaScript, where a sizeable part of the community writes in a style which de-emphasizes return values, it makes sense for jsig to have a good story for this use case. Do you have ideas for a clearer syntax? (Value) => doesn't sit well with me - an arrow pointing at nothing feels incomplete and ambiguous. My main issue with (Value) => void being inadequate for this use case is not that it's too long, but that it's artificially specific. The function is actually making no claims as to it's return value one way or the other, but this jsig expression is. The .Net framework has an interface called Action. From their docs, Action "Encapsulates a method and does not return a value.". We could support something similar using jsig Generic Types, but I don't want to create a "base interface prelude" for jsig if at all possible. I want jsig to be immediately approachable without context, but with a clear specification as reference material in case a particular notation is not clear. Your thoughts on alternatives are welcome.

Parameters: The symmetry with the ES 6 fat arrow and rest parameter syntax is wonderful, and I also like the ability to specify optional parameter names. This might be too special-case for jsig, but what do you think about mirroring the spread syntax for an arbitrary number of parameters of different types? As an example, consider this definition for Function#bind:

bind(thisArg: Object, [...parameters]) => Function

My intention was for this case to be covered by bind(thisArg: Object, ...parameters:Value) => Function - that is, each of the parameters is a Value, but not necessarily of the same specific type. The spec is rather hastily drafted - I welcome pull requests for language clarifications.

But further, what do you think about making types in function parameter lists optional? That is, if a function parameter is named but there is no type specified, then the notation is simply not making any claims or constraints about its type. This would make jsig more flexible, so that types could be states unambiguously where it matters and left unconstrained where it doesn't:

bind(thisArg: Object, ...parameters) => Function

It seems like this would be much more convenient for expressing these kinds of higher order function signatures where they don't much care about the types of lower level functions. (Someone with a stronger PLT would walk all over my last sentence, but I guess I'm referring to the amount that a programmer reading the type annotation would have to care, not a type checking compiler looking for provable safety guarantees)

Again, Kit, thanks for your comments. If you know of anyone else you think might be interested, please share - I'd love to start a broader dialog around this and see what people's needs are. In the mean time, I'm updating the documentation of some of my npm modules to adopt jsig.

kurttheviking commented 11 years ago

I've been turning this one over in my head as well. In my personal style I hate not returning (in fact, I like languages which return the value of the last expression in the function body by default, such as F# or CoffeeScript). However, for the purpose of JavaScript, where a sizeable part of the community writes in a style which de-emphasizes return values, it makes sense for jsig to have a good story for this use case. Do you have ideas for a clearer syntax?

So, I've been constructing some notes about your ideas @jden and this is the issue that initially felt strange when I read through the proposal. My uncertainty arises from the fact that void is--as you said--an artificial construct of jsig without inherit meaning in the described language.

Based on the comments and intent of jsig so far, it's clear to me the focus is on human readability...which, as a fellow human, I can appreciate. With that pragmatic approach in mind, it seems reasonable to me that the documented return value of a function without an explicit return is undefined such that:

(param1, param2) => undefined

Unfortunately, this approach is more verbose but I would also argue that it communicates intent in a more human-friendly way...particularly through the lens of expectation in assignment.

Raynos commented 11 years ago

For callback based functions I annotate them like

readFile :: uri:String -> cb:Callback<Error, Buffer> -> void

This is reasonably readable and expresses the important point that the function returns no value. Using something like cb:(Error -> Buffer -> void) get's really messy.

I've personally been playing with a haskell style type documentation recently ( https://github.com/Raynos/immutable-hash#documentation ) and I definitely like the ability to write single line comments above my functions documenting the types. This is a massive improvement over verbose multiline jsdoc statements

calvinmetcalf commented 11 years ago

@Raynos's suggestion would be helpful for functions that give a callback and/or a return value i.e. with if(cb){cb(null,blah)}else{return blah;}