I think if you really want implicit conversions, then you want to do what Perl does (never thought I'd say that): different operators for different types.
This is kinda insane. People can give reasons X is better than Y on grounds of various theoretical virtues of X -- that has nothing to do with a preference for any language.
Here's an example virute:
Behaviour should be consistent unless specialised. When specialised it should be obvious which specialisation is chosen.
(Violation: basically all of js' operators).
It's really easy to forget a closure in an event driven function and to produce difficult to debug, error prone code.
A classic example I just screwed up:
export function getSFTPConnection(options) {
let key = readFileSync(options.keyfile_path, 'utf8');
let ssh = new ssh2.Client();
return new Promise(
(resolve, reject) => {
ssh.on('error', reject);
ssh.on('ready', () => {
ssh.sftp((err, sftp) => {
resolve(sftp);
});
});
ssh.on('close', (err) => {
reject(new Error('SSH connection closed: ' + err));
})
ssh.connect(
{
host: options.host,
username: options.username,
privateKey: key,
}
);
}
)
}
The bug here isn't obvious, at least to me - but it caused me much heartache and pain because I was moving too fast and not thinking - but a good example of unexpected behavior by default.The bug is that there's not a closure around the ssh instance, so if you call this function multiple times it'll actually return the same connection instance.
The extra fun part of this bug is that the code worked - but because I was using the same instance, when I'd download multiple files in parallel, it would interleave the data, corrupting the files.
And the thing is, I know better. I've been doing js for decades.
I love js, but stuff like this is pretty frustrating, and can be a nightmare for new devs.
I’m not entirely sure what you mean by this, but it doesn’t sound like a correct diagnosis. Each call to `getSFTPConnection` creates a new `ssh2.Client` instance (unless `ssh2.Client`’s constructor does something really weird), and the promise can only resolve with the value `ssh.sftp` passes.
(The error handling does look broken, though – I would expect the 'error' event to be able to fire at any time, and the `ssh.sftp` callback is missing a check.)
Bad example!
You've either adjusted the code for posting or you misunderstood what the problem was, because no, that's not the case. The ssh variable is local to the getSFTPConnection function and is not reused between multiple calls to the function, and throwing a pointless extra closure in there wouldn't do anything.
- Lacks structural pattern-matching.
- Lacks first-class sum-types (although they can be clumsily encoded as objects with "kind" properties).
- Static type-checking not directly (or at all) contributing to the execution performance of the code. This is particularly egregious with e.g. CPython.
Presence of the first three are the sort of things that people appreciate in Good static languages (as opposed to the status quo static languages you alluded to that Python/Ruby/JS/etc were rebelling against in the 2000s).
The last is the dead giveaway of not utopia but rather a bizarre compromised situation.
The existence of `any`; worse yet, the use of `any` in the standard library.
Mutable arrays are treated as covariant: the classic `cats : Cat[] ; animals: Animal[] = cats; animals.push(dog);` problem.
Methods are both co- and contravariant in their arguments by default, which is comically wrong. Member variables which are functions, meanwhile, are handled correctly.
`readonly` is a lie:
const test: {readonly a: number } = {a: 0}
const test2: {a: number} = test
test2.a = 5
`Record<string, string>` is actually `Record<string, string | undefined>`. There's no way for an interface to specify that it really does return a value at any string index (e.g. for a map with a default value).`{...object1, ...object2}` is typed as the intersection `typeof object1 & typeof object2`, which is not correct.
const a: {a: 5} = {a: 5}
const b: {a: 4} = {a: 4}
const spread = <L, R>(l: L, r: R): L & R => ({...l, ...r})
const impossible: never = spread(a, b)
You have to resort to bizarre conditional type hackery to control when unions distribute and when they don't.You can constrain generics as `<T extends string>foo(t: T) => ...` but not `<string extends T>foo(t: T) => ...`, which makes many functions (e.g. Array.includes) much too strict about what they accept.