jsigbiz / spec

JavaScript signature notation
131 stars 6 forks source link

Expressing a function that mutates one of its arguments #44

Open Raynos opened 9 years ago

Raynos commented 9 years ago

I wanted to express a function that mutates it's arguments today.

function createUncaught(opts, clients) {
  clients.onError = function (err) {
    ...
  }
}

This function does not return anything, it could be expressed as

createUncaught : (opts: Object, clients: {
  onError: ((Error) => void) | null
}) => void

but that is kind of ambigious. I can't think of any better way to express this.

cc @jden

pirfalt commented 9 years ago

I have not used jsig much, I just like the ide of it. So this is not from experience, just my thoughts.

alt1: Anyway, I would just write this as createUncaught : (opts: Object, clients: Object) => void. Keep it short, keep it ambiguous and make that clear. If a function signature cannot express the function clearly, the best you can do is to highlight that fact.

alt 2: Steal the ! from lisps set! and use a naming convention to indicate mutable input just as ? indicates optional input. So createUncaught : (opts?: Object, clients!: Object) => void with optional opts and mutable clients.

Raynos commented 9 years ago

I like supporting !

createUncaught : (opts?: Object, clients!: Object) => void

or

createUncaught: (opts: {
  clients: (mut!: Object),
  logger: Object
}) => void

Here using ! in the label means this value will be mutated.

Also note that you can express that a value in an object is mutable by using the fact that values in the object literal type syntax can have labels. I used the braces for clarity here.

Note that I would not suggest extending the object literal type syntax to allow { foo!: String } as that would create unnecessary ambiguity.

Matt-Esch commented 9 years ago

! tends to mean not. I would rather take one of the more obvious indicators that are similar to existing syntax

(ref { foo: String }) => void
(* { foo: String }) => void
Raynos commented 9 years ago

Adding a new keyword to jsig feels expensive.

Having a combinator for labels feels like a less invasive change.

Matt-Esch commented 9 years ago

Adding a keyword feels like not breaking back compat.

Raynos commented 9 years ago

foo!: was never a valid label as per the parser in the past.

junosuarez commented 9 years ago

I prefer ! to a keyword, especially a short string like ref which doesn't actually relate to a concept in JavaScript semantics. I also agree that this behavioral expectation is best not expressed in a function signature.

@Raynos can you describe more about your use case or why you felt the jsig in your original post was ambiguous? This is a great example, in my mind, where human documentation needs more than just type annotation to communicate programmer intent.

I know youve done a great deal of work on tooling around jsig - is the extra knowledge encoded by having a "mutated parameter" bit useful for tooling?

Raynos commented 9 years ago

@jden

The following

createUncaught : (opts: Object, clients: {
  onError: ((Error) => void) | null
}) => void

Actually means I take two arguments. opts and clients. clients is an object with one key whose value is either a function or null.

However if you call this function with createUncaught({}, { createError: function () {} }) then you will probably be suprised that your argument gets mutated and that the createError function is different.

What I would like to have is

createUncaught : (opts: Object, clients!: Object) => void

Here I say I take an opts object and a clients object. The clients object will be mutated by this function.

Note i cannot communicate how it will be mutated but you have the correct expectations, I must pass it two objects, the second object will be mutated in some, I should read the docs.

The first interface has the danger of someone passing it a thing and it being mutated to have the SAME interface but different implementation. This can be very confusing.

If I were to not use ! or ref I would document it as

createUncaught : (opts: Object, clients: Object) => void

Which just has no information that is ambigious and definitely requires reading the documentation.

junosuarez commented 9 years ago

Note i cannot communicate how it will be mutated but you have the correct expectations, I must pass it two objects, the second object will be mutated in some, I should read the docs.

I like this, for being able to flag to someone reading through signatures that they might need to better understand what change is happening.

To clarify, because we're in JavaScript, just because a function signature doesn't have a ! wouldn't be a guarantee that the arguments aren't being mutated. ! would be more of a hinting modifier signaling that it will be modified.

One aspect of the proposal that is a little troubling is that it doesn't indicate how the argument is mutated. The mutation could potentially change the type of the argument, resulting in type erasure (meaning that tooling would have a much harder time reasoning about types - or potential type unions - for variables in the program).

If I were to not use ! or ref I would document it ascreateUncaught : (opts: Object, clients: Object) => void Which just has no information that is ambigious and definitely requires reading the documentation.

This is the same level of information that you could actually infer about the type of the argument, with or without a ! modifier.

Raynos commented 9 years ago

@jden

I would say it's a very common practice to not mutate function arguments.

The only time you do mutate is either very small specific functions like a linked list implementation or mutating this.

The act of mutating this is very common, where as mutating other function arguments is significantly less common.

It should be noted that this practice depends on what your writing, I'm mostly using jsig to document libraries which do very little mutation. If you were to use it to document an application or business logic you might do a lot more mutation.

junosuarez commented 9 years ago

@Raynos okay, I think we're agreed on the use cases in terms of indicating explicitly when to expect that an argument may have been mutated, and on ! as a postfix operator on arguments.

Would you care to take a stab at defining the semantics of !?

Raynos commented 9 years ago

Yeah I can take a stab at it :)