Open Raynos opened 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.
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.
! 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
Adding a new keyword to jsig feels expensive.
Having a combinator for labels feels like a less invasive change.
Adding a keyword feels like not breaking back compat.
foo!:
was never a valid label as per the parser in the past.
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?
@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.
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 as
createUncaught : (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.
@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.
@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 !
?
Yeah I can take a stab at it :)
I wanted to express a function that mutates it's arguments today.
This function does not return anything, it could be expressed as
but that is kind of ambigious. I can't think of any better way to express this.
cc @jden