993 karma · joined October 11, 2013
When compared to doing nothing and tolerating them, I can definitely see that. But I still think there have to be more effective approaches than either (even if we don't know what some of them are yet).
inputs (sensors, environment, external chemicals) + current state ---> outputs (behavior) + new state
kinda like computers or robots, in a deterministic manner (except it is practically impossible to recreate the exact same inputs, or the exact same state). You don't have to take it too literally, people who read it generally understand what it means.
What I was getting at is the fact that there is no fundamental difference between "I did it" and "my illness caused me to do it" (other than maybe probability).
Or in other words, no "free will", in the way that some people view that term.
That is such an arbitrary distinction. As if being "just a slacker" doesn't have a number of deeper reasons behind it as well. Brains are just machines at the end of the day, every behavior is a result of something material...
I see very little practical value in a DSL like this. Using an existing language with an extensive ecosystem and great tooling was half the point of wanting TypeScript in the first place. Most of the "risks" can be handled with just a few eslint rules, tests (e.g. all outputs must satisfy a set of constraints), and code review.
Especially when the moment you need to do more than just generate config files (e.g. fetch some information from an external service, or even write an imperative script for the occasional task), you'd have to switch to a different langauge and all its tooling - where you can't just import the exact same type definitions, etc.
I was picturing something more low level and less opinionated, though I can only guess how such a thing would perform in practice.
Not even necessarily a specific product, just the adoption of workflows that use already existing tools (e.g. the various API client libraries - for AWS, Kubernetes, git, docker registry, etc.) to achieve this. I realize that that could lead to reinventing the wheel, and eventually an in-house framework anyway, but it feels a lot more comfortable to start there, as you can design things closer to your exact needs.
Pulumi definitely looks interesting though.
The domain is not simple enough for that, so you end up with all kinds of weird scripts around it all, or other templating/codegen solutions, plus all kinds of ad-hoc conventions that are extremely easy to break and very annoying to navigate.
Having a full blown statically typed language like TypeScript - where things can reference each other strongly (not just by names and other strings), naming conventions can be encoded in functions (or even enforced by types), and structures on every level (even your project-specific ones) have a well-defined and checked schema - would help a lot in my opinion.
Maybe deno could find a use in this niche.
Years later, the same kind of slowdown happened with tablets (especially Android tablets which are basically dead, but iPad wasn't completely spared either), and will eventually happen with smartphones as well.
The "we are now in a post-PC era" hype of 2010 (after the iPad's release) has eventually led to many "tablets are dead" articles by 2016.
I wonder whether a "richer" editor/IDE, that flags such things immediately upon writing them, could greatly improve the learning experience.
Many tools even point out things such as non-existing variables in scope, which are not strictly "errors" in a dynamic language during edit time.
I have spent far too much time on HN recently in related discussions.
Having access to libraries like React and its huge ecosystem is a big plus for productivity (example vs. Qt at https://news.ycombinator.com/item?id=23154315).
Building custom UIs is also very easy with the flexibility that HTML provides (https://news.ycombinator.com/item?id=23164595), even though it can be done with other technologies (and there are some examples of that).
Kotlin + TornadoFX + native-image (if it is supported) sounds like a pretty comfortable to use UI stack.
There are tons of existing cross-platform UI frameworks (Qt, wx, libui, JavaFX, TornadoFX, ...). The comment above says "Everybody would jump to Swift [for new projects] if it meant a cross-platform GUI framework."
And yet people are still wondering why Electron ends up being the technology of choice for so many desktop apps.
return
value
which will get flagged by TS as unreachable code (you can try it in https://www.typescriptlang.org/play)A lot of the screens show FPS info, so it's not immediately obvious.
Can I do stuff like `Partial<T>`, `Omit<T, K>` intersection types, etc? Is structural typing the "default"?
import { dependency } from 'http://whatever.url/@1.0.3'
with hash '6f09aa686a6263f9e992'
or something like that. If an attacker replaces stuff during transfer of the dependency, then the hash won't match (assuming a collision resistant hash function). This can be transitive if the dependency also has hashes of its dependencies directly inside it (and a "only allow that" mode could enforce it).The additional benefit is that you also don't need to trust the server to not replace code for a URL under your nose from one you have previously verified. So like a lockfile, but without needing to download a separate file (because you also want verification on the first run) and a command-line argument on launch - which makes it a much more viable replacement for bash scripts (in fact, that's why I care about the ability, I wouldn't mind https-only). Am I missing something here?
EDIT: I mean hashes of dependencies.
That is important for single-file scripts, if this is meant to be useful as a bash replacement for scripting.
Having to download two separate files for a script and execute it with special arguments already adds too much friction to that scenario.
E.g. `const a = [1, 2, 3]` will let you do `a[5]` and still give it type `number` instead of `number|undefined` as mentioned in the github issue. Many other languages also do this (though they have runtime checks/errors).
So that kinda sucks, but the rest still stands (there is still no practically realistic way for `null` or anything other than `undefined` to get there). Hopefully they'll fix that in the future with a new compiler setting.
In practice, say I have
type User = { name: string, email: string }
let loggedInUser: User = { name: 'Bob', email: 'bob@examaple.com' }
or even // Can't change the contents, only reassign the whole variable
let loggedInUser: Readonly<User> = ...
and only ever use that `loggedInUser` from TypeScript code (because my entire codebase is TypeScript), and I avoid the 3 scenarios mentioned above (buggy JS library type definitions, non-validated external data, casting/any/`!`).How would a `null` email ever get inside `loggedInUser`? It just won't happen. I know there are no run-time checks - I don't need any run-time checks. I don't need 100% theoretical guarantees. I can be reasonably certain that `loggedInUser.email` will always be a non-null non-undefined string. It's a good enough™ practical guarantee. A null-check on `loggedInUser.email` would be pure confusing noise (because anyone reading it would go "wait, this can be null? I thought it was non-null").
Do you have a practical explanation of how a `loggedInUser.email === null` situation might occur given the above assumptions?
>Given any JS variable can be assigned both `undefined` and `null`
Any JS variable can be assigned literally anything. I don't need to check that `loggedInUser.email` is not null, just like I don't need to check that `loggedInUser.email` is not a boolean or a number. Because how would that even happen? Not having to do those checks is half the point of having a static type system in the first place (the other half is being notified of type errors at compile/edit time).
If you need to defensively check whether something so basic that is never supposed to happen has happened, then the code is already a giant unmaintainable mess.
But there are only a few ways in which that can happen:
- a bug in type definitions - a JS library with .d.ts files which "lie" about the actual types - this is no different than any other bug in a library, needs to be reported and fixed. In my experience, such bugs are not very common (I've yet to encounter one).
- non-validated external data (JSON coming from HTTP) - like I said, you have to validate external data (or assume that anything can be anything). You kinda have to do it in all languages, though ones with reflection and a deserializing library can do that somewhat automatically and throw a runtime error if the deserialization fails (although that is not full validation).
- incorrect explicit casts and using `any` - e.g. `const a: number = ('asdf' as any)`, but less stupid and more accidental. This is a bug in the casting code, similar to doing an incorrect `reinterpret_cast` in C++. There is usually little reason to do explicit casts in TypeScript code, outside of very small isolated pieces of code.
Which means you generally don't have to worry about any of this. Relying on those guarantees is good enough for 99+% of situations (I mean, some people even use dynamically typed languages and survive without doing tons of checks on every other line of code).
---
Testing stuff which is not expected (by the specified type) to be `null` for `null` is just as pointless as testing it for the number 10. There is just no "valid" way for the `null` to have gotten there (just like the number 10). It would be extremely paranoid and only add noise to the code.
If there is a valid way, then the type needs to be `T|null` (and then you need to test it), not `T`, otherwise it's a bug. For TypeScript code, this will be checked automatically, for JS library type definitions, it needs to be made correct manually.
Regarding JSON and undefined, you can omit properties in JSON. E.g. sometimes have {"user": {"name": "Bob", "email": "bob@example.com"}}, and sometimes just {"user": {"name": "Bob"}} without the email. Such an optional "email" can of course be expressed in TypeScript, and can be tested as `=== undefined`, which will return true if it's not there. And such a pattern of omitting properties from objects (not just JSON) is much more common in JS than assigning `null` to stuff.
You are much more likely to find a JS object where some property is sometimes there and sometimes not there but never `null`, than an object where it is always there but sometimes `null`. That's what I mean when I say `undefined` is much more common than `null` in the JS world. But neither is a problem, you just need to have types which correctly describe what values the properties can contain.
---
So typically none of this is a problem in practice, as long as you have correct type definitions for the JS libraries which you use (i.e. there is no bug in them, just like there should be no bugs in the library itself) and don't do funky casts with `any`.
So if I have a type `User|undefined`, testing `if (user === undefined || user === null)` is pointless, as `null` is not part of that type. It would be different if it were. Similarly, it makes no sense to test a variable of `User` type for value `false`, like it would if the type were `User|boolean`, or even `User|false` which you can do in TypeScript (not that you should). So `if (variable)` is still bad (ok for booleans, tolerable for some types but better avoided). You only test for what the variable can contain.
Some types you encounter are `T|null` - mostly DOM things (e.g. the return value of getElementById), but some JSON stuff as well like you said. Even though a lot of JSON just has optional properties instead, which for all intents and purposes work like `|undefined` when reading them.
From my experience, it is much more common to deal with `undefined` in the JS ecosystem, whether that is by omitting properties from objects, or explicitly assigning/returning `undefined` somewhere.
`T|null|undefined` types are ever rarer. Only for those it makes sense to test for both null and undefined.
By the way you can also have types like
type RemoteData<T> = {ready: false} | {ready: true, data: T}
in TypeScript, which is better than { ready: boolean, data?: T }
because you don't have to worry about "does `data` still make sense when it is non-ready?", the type clearly tells you that it doesn't (of course you can also have a different type where it would, the point is to accurately describe the structures you deal with). Then, if you do a check if (remote.ready) {
console.log(remote.data.length)
}
it knows that data exists inside that `if` block, so you can safely access it without `!` or `?`. Similarly, inside `if (!remote.ready) { }`, you cannot access it at all. It also won't let you pass an object with { ready: true }, but no data of the required type to `RemoteData<T>`.More often this can happen with external data (typing incoming JSON responses), but you have to run-time validate those anyway.
`null` is only rarely used in JS, `undefined` is by far the more common one, and of course the types reflect which one is a possible value, so it's not like you can miss it by accident.
Falsy values are only a problem with bad practices - inside `if`, I just always use the full form with the `===` operator, e.g. `if (variable !== undefined)` instead of `if (variable)`. A lint rule can be enabled for that. There is now also the `??` operator which prevents the common misuse of `||`, e.g. `const a = variable ?? 'default'`.
So those things aren't really issues in practice.
You are correct in assuming that I'm not very familiar with Dart, I have only used it very briefly. I have only checked whether specific features such as sum types were available.