[HN Gopher] Tricks I wish I knew when I learned TypeScript ___________________________________________________________________ Tricks I wish I knew when I learned TypeScript Author : cstrnt Score : 492 points Date : 2021-10-12 07:51 UTC (15 hours ago) (HTM) web link (www.cstrnt.dev) (TXT) w3m dump (www.cstrnt.dev) | jjice wrote: | I'm so happy powerful type systems are more popular now. | TypeScript bringing a great type system to a language as popular | as JS is fantastic. Rust is also a great way to get a great type | system while staying in a systems programming and procedural | environment. Hell, even python type annotations support union | types. | | I never knew the depth of type systems until the last year when I | took a type theory course and a compiler course taught by a man | who loved his types. So much power - I can't wait to see the | future of type systems. | jcrben wrote: | What course? | dreamer7 wrote: | Could you point to any good resources on this topic? | ebingdom wrote: | The standard reference, if there is one, is Benjamin Pierce's | "Types and Programming Languages" book. | marginalia_nu wrote: | The grass does seem tend to appear greener in the other | paradigm. | | Barely typed languages like C made rigorously typed languages | like C++ and Java seem appealing. The boilerplatiness of those | languages made duck typing seem appealing. Writing anything | nontrivial with duck typing made more elaborate type systems | seem appealing. | | Needing a PhD in category theory to produce a side effect will | no doubt make some other paradigm seem appealing in the future. | machiaweliczny wrote: | TS has good option of being optional. I programmed C++ (rigid | niminal), Ruby (duck), Haskell and TS is best compromise so | far as it works like tested documentation which is most | practical for many purposes (where program must be iterated). | If you know what you want to build beforehand then sound | typing might be better. | ebingdom wrote: | > Barely typed languages like C made rigorously typed | languages like C++ and Java seem appealing. The | boilerplatiness of those languages made duck typing seem | appealing. | | Eh, I consider Java to be barely typed too. If you have a | variable of type Foo, the type system doesn't even guarantee | that you have a Foo in there (it might be null). The whole | point of a type system, in my mind, is to guarantee that I | have that Foo! | | > Writing anything nontrivial with duck typing made more | elaborate type systems seem appealing. | | In my mind, this makes type inference seem appealing, not | duck typing (which is not well-defined, but most people | associate it with dynamic typing). | | > Needing a PhD in category theory to produce a side effect | will no doubt make some other paradigm seem appealing in the | future. | | This oft-repeated exaggeration needs to stop. Using monads | does not require a PhD in category theory. If you can | understand Promises in JavaScript, then you can grasp how IO | works in Haskell. | marginalia_nu wrote: | > Eh, I consider Java to be barely typed too. If you have a | variable of type Foo, the type system doesn't even | guarantee that you have a Foo in there (it might be null). | The whole point of a type system, in my mind, is to | guarantee that I have that Foo! | | That seems a rather arbitrary limitation. When Java says | Foo, it would translate to what you would consider | Maybe<Foo>. How you represent types, and what that | representation implies is a matter of semantics. | | That said, Java's type system is pretty dang weird, | especially generics. | | > This oft-repeated exaggeration needs to stop. Using | monads does not require a PhD in category theory. If you | can understand Promises in JavaScript, then you can grasp | how IO works in Haskell. | | The concepts themselves aren't particularly hard to grok, | but the more academic side of functional programming (i.e. | Haskell) is comically inaccessible, mostly due to jargon | (monads aren't even that bad compared to something like | kleisli arrows). | tshaddox wrote: | "Maybe<Foo>" doesn't have the same problem as Java nulls | though, because with an optional type the type system | would force you to always handle the possibility of the | value not being present. With Java nulls you just get a | runtime error if you try to do certain things with | something that turns out to be null. | | Incidentally, Java does have Optional, and codebases | which use it consistently are vastly better to work with. | https://docs.oracle.com/javase/8/docs/api/java/util/Optio | nal... | throwanem wrote: | I'd love to see an explanation of monads that accurately | captures their capabilities in terms no more complex than | those required to do the same for promises. | knubie wrote: | If you understand promises you pretty much understand | monads already since promises are more or less a type of | monad. | | Monads represent computation contexts. In the case of | promises, the computation is preformed in the context of | a value that will be available some time in the future | (or not at all in some cases). | my_promise.then(compute_result) | | Another context could be a list, where the computation is | performed on each value in the list | my_list.then(increment) | | Or the context could be that the value is maybe null | maybe_string.then(uppercase) | | How the computation is actually performed depends | entirely on the monad. Usually .then is called .bind or | .flat_map because it will automatically unwrap nested | monads. | Choc13 wrote: | I had a pop at this here https://dev.to/choc13/grokking- | monads-in-f-3j7f | goto11 wrote: | Monads are a pattern for function or method chaining. | dboreham wrote: | Not an explanation, but they key thing to know is that | monad is not a thing, but a pattern. It's a pattern for | composing things. Like the FP equivalent of the Unix | shell pipe character. Like the Unix shell provides | composability for processes that happen to read from | stdin and write to stdout, most FP languages provide a | framework for composing monads (of whatever kind) neatly | e.g. Scala and Haskell "for comprehensions". | [deleted] | ipaddr wrote: | Monad: In functional programming, a monad is an | abstraction that allows structuring programs generically. | Supporting languages may use monads to abstract away | boilerplate code needed by the program logic. | | Promise: A Promise is an object representing the eventual | completion or failure of an asynchronous operation. | Essentially, a promise is a returned object to which you | attach callbacks, instead of passing callbacks into a | function. | jmfldn wrote: | Monads are chainable containers for a computation. The | container represents some sort of "effect" implicit to | the computation ie asyncrony, optionality etc. This is | not technically 'correct', monad explanations are always | a bit cursed, but I find this a pretty useful intuition | to work with day to day. | kaba0 wrote: | To understand Monads, we have to first understand | Functors, Monoids and the Applicative type classes (type | classes are more or less the same as interfaces in Java, | C++ and the like). | | A Functor is something that implements a `map` function. | You can think of a Functor as anything that can | encapsulate/surround something else. The map function | applies a function to the encapsulated element without | modifying the outer structure. For example, a List is a | Functor, that has this map function implemented in most | languages. It indeed surrounds a given type (zero or more | item of that type to be more correct), and it indeed | applies the same function over each element of the map. | Several languages have a "Result/Maybe/Optional" type, | that can either contain one instance of a type, or | Nothing. Some languages allow you to modify the inner | element, when it exists, that is, it is also a Functor | that encapsulates an element and has a map function to | change that. | | Let's dissect this Monoid word next (which is not a | Monad!). Before the definition, let's look at an example: | summing numbers. Let's say we have a list of numbers, and | we want to calculate the sum of it. We can start from the | beginning and go through the list one by one, or sum the | first half and the second half first, and then add them | together. These are possible because the add operation is | a Monoid over the numbers. What that means as an | interface (type class) is, that it implements an `empty` | function, a so called neutral element (0 for addition), | and a `concat` one (which is + itself). | | These names make us think of Lists, and indeed, Lists are | Monoids as well, not only Functors. They have an empty | method returning [], and they have a concat function that | concats two lists together. Do note that concating an | empty list to a list doesn't change it. | | Is a Return type a Monoid? We could make the case for | empty being Nothing, where concating Nothing changes | nothing, but what about concating two Results both | containing an integer? Should we sum them or give the | product, or write them next to each other? | | It turns out that (in Haskell at least) you can do | specify something like, this is only a Monoid if the | embedded type it has is a Monoid. So for example that way | a concat(Just [1,2], Just [3,4]) will return the | concatenated list _inside a result type_. | kaba0 wrote: | _Continue_ | | We are almost there! Let's tackle Applicative now. What | can we do if not only are data is encapsulated in some | structure, but even our function which we want to apply | to? | | Can we apply a list of functions over a list of values? | Or an Optional/Result function over an Optional/Result | value? Does it even make sense? | | Let's define Applicative as being a Functor that has a | `pure` function similar to our Monoid, as well as a | `sequentialApplication` one that we will shorten as the | cryptix < _>. Since it is a Functor, remember that it | also has a map function available. | | The pure function simply encapsulates a type inside | itself. For example, a list constructor is precisely | that, like python's list(2, 3) creating a new list. The | <_> is a bit more complex, it takes as parameter a | function that is encapsulated in this very type and | operates on type A. Its second parameter is an | encapsulated object containing type A. And the important | thing comes here: it will apply the encapsulated function | to an encapsulated data, without unwrapping first. | | Let's say, I have a text field where the user Maybe | entered his/her name (sorry) and a field where we await | an age. We'll store these inside a Result<String> and a | Result<Age> type. | | Let's say we have a User constructor that awaits a name | and an age. We could handle each parameter separately | here, but what if we have 20? So we instead do something | like pure(createUser) < _> nameResult <_> ageResult. | This magic line will apply the Just createUser function | (that is, wrapped into a Result with an existing value | due to pure) to a possibly missing name. Let's stop for a | moment here and talk about currying. In many FP | languages, calling a function with less parameters is not | a compile time error. It gives back a new function that | awaits one less parameter. Eg `+ 3` is a lambda that got | its first parameter as 3, and will "execute" when we give | it the second parameter. | | Knowing this, our evaluation so far will be something | like Just (createUser(nameResult if it has a value)) or | Nothing. Now we apply this, yet again enwrapped function | to the last parameter, so we will get as a resulting type | a Result<User>, which will contain a user when both | values were sent, and will be Nothing if any of them were | absent - cool isn't it? | | But I know, we are here for Monads! | | Well, Monads are just monoids in the category of | endofunctors. Just kidding. They are Applicatices, that | also have a `bind` method. | | So you've seen how we could "sequentially apply" | functions. But the "problem" with Applicatives, is that | we can't depend on the output of a previous function -- | in the previous example we could not have ageResult's | evaluation change depending on what was returned | previously. Let's see another Applicative, the often | misunderstood IO. | | putStrLn has the following type: String -> IO (). | | That is, it waits a string and gives back an IO structure | that returns void (actually it is called Unit). If we | were to somehow execute it, it would print that string | ending with a newline. If we do | | (x => putStrLn("world")) <*> putStrLn("hello"), it will | output (if we know how to execute it) hello world in two | lines. The reason for this strange ordering is, that we | apply a function that drops its first parameter to the | second one. (Haskell does have a shorthand for this!) | | But how can I act upon the result of a previous | computation in a "sequential application", eg. read in a | line and print hello $name? By `bind`! It does the | following: it needs a monad at hand, with encapsulated | type A (eg. IO String for the readline we will use), a | function that takes that type A (String) and returns this | monad with any type (we will print out the string so we | will use putStrLn) | | So, bind(getline, name => putStrLn("hello $name")) will | do what we want and you have just used your first proper | Monad (Haskell of course provides a nice syntactic sugar | over this called do notations) | | With these abstract structures, IO can be dynamically | constructed and side effects will only happen where we | expect them. | jmfldn wrote: | I would go further. Using monads, or any functional | programming concept that you'll encounter in the wild, | requires zero category theory. I would actively warn anyone | against learning category theory for day-to-day use of FP. | Learn it if you're interested in it for its own sake sure, | it's a great maths subject, but it's basically irrelevant | for most programmers imho and a distraction if you're | trying to get a simple practical understanding. | underdeserver wrote: | I know dozens of people who tried understanding Promises | and all of them succeeded. | | I know dozens of people who tried to understand monads | (including myself) and maybe 3 of them succeeded (I do not | consider myself one of them). | jokethrowaway wrote: | Monads and Promises are not comparable. The IO Monad | fulfills a similar role to Promises and people learning | Haskell grasp them immediately. Haskell also has an | equivalent of the async/await syntax called the do | notation which makes things nice and easy to read. | | You don't really need to understand monads to understand | the IO monad or Promises. If you want to understand | monads, look at their signature, mainly the bind operator | and try to implement something with it. A logger, a list, | the IO monad itself. It's not that hard, it's just that | nobody bothers playing with it and just try to learn the | theory without experimenting with monads in code. After a | few tests you'll build an intuition for it and you'll get | why they call them programmable semicolons. | | Monads is just one of the abstractions that can be used | to implement IO in Haskell btw, it was just the authors | flexing their category theory that got us in this | situation. | | At the same time, one of the reasons I learned Haskell is | that it had a reputation for being hard and, boy, am I | grateful for it. | piperswe wrote: | Basically, think of a monad as a Promise. Rather, a | Promise is more-or-less an example of a monad. (Yes, I | know that due to some technicalities it isn't, but it | behaves like one for the purposes of this comment) | | Mapping a monad is equivalent to Promise#then - if | there's a value in the monad, then it calls the function | you passed to map and returns the result wrapped in a | monad. If there isn't a value in the monad, then it | returns itself. | | For example, with the Maybe monad, if you have x = | Maybe.Some(y), then x.map(f) = Maybe.some(f(y)). If you | have x = Maybe.None, then x.map(f) = Maybe.None. With | Promises, if you have x = Promise.resolve(1234), then | x.then(x => x * 2) = Promise.resolve(1234 * 2). If you | have x = Promise.reject(new Error('abcd')), then x.then(x | => x * 2) = Promise.reject(new Error('abcd')). | | The IO monad is extremely similar to Promises - you call | an IO function and get an IO monad as a result, then map | that monad in much the same way you'd then a promise. | jokethrowaway wrote: | While we are here, Futures from fantasy land (in js) are | better promises because they don't execute immediately | and they need to be run manually | toxik wrote: | A monad is just a monoid in the category of endofunctors, | what's the problem? | dboreham wrote: | Monad imho is deliberately badly explained to preserve | the mystique and the smugness of the cognoscenti. I found | this book invaluable for translating the field into | something that makes sense: | https://alvinalexander.com/scala/functional-programming- | simp... | afiori wrote: | Not arguing for or againts something, this is just | something I wanted to say. | | Framing matters here, the API of Promises in js maps | almost nicely to the common monad API of bind (then), | join (implicitely done by the runtime whenever possible), | and return (Promise.resolve). Learning any monads is | unlikely to be harder than this as these operations are | indeed quite intuitive. | | What is often referenced with learning monads is instead | Learning All the Monads, sometimes adding a touch of | Learning All the Monad Transformers and interactions of | different monads. This intrinsecally needs to be done in | a generic/parametric way and is way harder. | | To my knowledge Haskell is the most mainstream[1] | language only typed language that allows expressing the | categorical definition, most other type systems are | significantly more limited in how much maths/category | theory they can express and often focus on specialized | functionality with practical implication like Rust's | ownership system or Typescript's Capitalize<StringType> | that only exists to allow nicer typing of some common API | desings[2] | | [1] most mainstream typed language at least, you can | implement Monad is JS/TS but the language cannot express | it the same way C cannor express generics even if you can | manually implement them in it. | | [2] https://www.typescriptlang.org/docs/handbook/utility- | types.h... | kaba0 wrote: | Otherwise agree with you, just noting that Scala is | likely even more mainstream than Haskell and has proper | Monads. | nicoburns wrote: | > Needing a PhD in category theory to produce a side effect | will no doubt make some other paradigm seem appealing in the | future. | | I think it's only really Haskell (and perhaps languages like | Idris) that's super strict on side effects. | | In Rust it's a simple mut annotation, and perhaps a mutex | (and you'll want that in C too of course) if you're working | across threads. | knuthsat wrote: | Does rust have Mut annotations on functions? | | I mean, when you look at a problem that monads solve with | types is that every function has an "annotation " of what | it uses (IO or mutable state). Similar how async in JS | allows await, a state annotation would allow put get or IO | annotation any of the IO capabilities. | | Of course monads are much more but Mut does not look like | it pollutes everything with Mut, including function | definitions and results | lalaithion wrote: | No, it does not. | whimsicalism wrote: | Effects systems in Scala | kazinator wrote: | C is not "barely typed". It has quite a lot of type checking. | | An expression like "obj.memb" in C requires obj to be | declared to have a type which has a member "memb". | | C catches it if you call a function with the wrong number of | parameters, or wrongly typed parameters, such as passing a | "struct foo *" pointer where a "struct bar *" argument is | required. | | C has "holes" in the static safety net in areas like memory | safety: object boundaries and lifetimes. It allows some | unsafe conversions, like any object pointer to a void * and | back. But not only those: C has unsafe numeric conversion and | operations. | | Still, there is a type system there, and C programs greatly | profit from it; it's the big reason why we have so many lines | of C code in our computing infrastructure, yet the proverbial | sky isn't falling. (Just the odd lightning or hail here and | there.) | | C compilers also help; modern compilers have a lot more | diagnostic power than compilers thirty years ago. In C, it is | critically important to diagnose more than the bare minimum | that ISO C requires. For instance, whereas a function that | has not been declared can be called in any manner whatsoever | (any number of arguments), it's a bad idea to do that without | issuing a diagnostic about an undeclared function being used. | If such a diagnostic isn't enabled by default it's a bad idea | not to add that. C programmers have to understand the | diagnostic power of their toolchain. | | Recently, GCC 11 found a problem in some code of mine. I had | converted malloc/free code for a trivial amount of memory to | use alloca. But somehow I left in a free call. That was not | diagnosed before, but now it was diagnosed. | | Another obscure bug that a newer compiler with newer | diagnsotics caught for me in the last few years was a piece | of code where a comparison like this was being made: | d <= UINT_PTR_MAX | | where d is a _double_. The idea was to try to check whether d | is in the range of a certain integer type before converting | it. Trouble is that the above expression moves the goalpost | because when UINT_PTR_MAX is 64 bit, then its value is not | necessarily representable in the double type. What happens is | UINT_PTR_MAX is converted to double, and in that process it | goes to a _nearby_ double value which happens to greater than | UINT_PTR_MAX! And so then the range check becomes wrong: it | includes d values in that extended range, which are beyond | the range of that integer type, causing undefined behavior in | the conversion. | afiori wrote: | In the field of formal type systems two common approaches | to defining types, in practice they are quite similar but | in my opinion they differ a lot in framing. | | One side can be represented by Haskell, Hindley-Milner type | systems, or even Coq; here every value has its own "best" | type that is intrinsecally associated with it, that is | values and types are defined and constructed together. | | On the other side you have sort of a formal definition of | duck-typing; you have values and properties that are | satified by some set of values, here you have your values | (all numbers, all strings, all memory addresses) and expres | in usual logic terms any property you want (e.g. this | memory address must be either Null or point to a string of | even length). | | All this to say that C has a nice type system from the | first point of view (function pointer allow you to have | higher order functions!) but a very weak one from the | second point of view in that it is very hard to decide if | an operation will have a valid result just by the types of | the values you feed into it (let's not talk about UB for | now). | | In my opinion in later decades there is a movement to care | more about type systems that follow the second approach. In | my opinion it is one of the reason for the success of | Typescript; its objective wasn't to have a nice type system | full of good properites, but to model how javascript was | being written. | kaba0 wrote: | C is a statically, but weakly typed language with very few | compile time checks and many non-intuitive automatic casts. | dllthomas wrote: | I think the major shift was that originally types were used | mostly to talk about representation. Then we wound up in a | situation where caring that much about representation didn't | make sense as often (at this point it's at system boundaries | and when we care an unusual amount about performance) and so | it made sense for some languages (covering a growing portion | of programming) to stop talking about representation. But it | turns out there are other useful things that we can use | similar technology to "talk about" as we learn to build | better tools and to better apply them. | shepherdjerred wrote: | You don't need a PhD, you just need the first few chapters of | [Category Theory for | Programmers](https://github.com/hmemcpy/milewski-ctfp-pdf) :) | dboreham wrote: | The wheel of typing. | jeswin wrote: | Here's another. Instead of returning Sometype|undefined from a | function which may or may not have a value to return (such as | searchCustomer), return Sometype|null. | | That forces the function to return a value that's explicitly | intended rather than defaulting from a missed out if-else | codepath. This is useful since JS is often imperative style code. | hamstercat wrote: | The difference between null and undefined in JavaScript is | something I wished had never been implemented. Other languages | refer to null as their billion dollar mistake, but somehow | JavaScript got 2 of them with slightly different but sometime | identical behaviour. I would defer to eslint to prevent this | particular issue if you care about it, this allows you to set | rules in your own code without any impact to the outside world. | | I have only seen null vs undefined lead to 2 things in my | experience: mistakes and bikeshedding. | Vinnl wrote: | When people refer to the "billion dollar mistake", they mean | the language feature that every value could potentially be | `null`, i.e. even if a function returns MyType, it could also | return `null`. | | TypeScript in strict mode (the default) still has `null` and | `undefined`, but not the billion dollar mistake: if you want | to be able to pass `null` to a function, you have to mark | that parameter as being potentially `null`. | IggleSniggle wrote: | I disagree. `null` in TypeScript is equivalent to `None` in | many other typed languages. `undefined` in Typescript is like | null in other languages, with the caveat that if you're | working to transition an untyped codebase and trying to bring | types, there may be a useful place for `undefined` in order | to express that there is a lack of safety / strict-handling | in that area. | | I'm still not sure about Error handling, though. Seems | feasible that in a _fully_ typed project, any possible | unhandled error type could raise a compile error. AFAIK | there's nothing (beyond catch + exhaustive switch) to handle | exhaustive error checking in TypeScript, nor is there lib | support for handling it either. | remram wrote: | Which language has both a "None" and "null"? | efficax wrote: | javascript, at least, has "undefined" and "null", an | infuriating duality of falsiness. PHP also has a notion | of not being set as well as being set but null. | remram wrote: | JavaScript is what we are talking about in this thread, | making the point that JavaScript is similar to JavaScript | does not help ;-) | [deleted] | mynameisash wrote: | Scala, as I recall. It encourages using Options | (Some/None), but since it runs on the JVM and will often | interop with Java libraries, you can also have nulls. | | Not exactly a language design, but an unfortunate | reality. | remram wrote: | Oh no! Does it not have a type for NonNull references, | like Rust does? | kaba0 wrote: | Since Scala 3, it can have total static analysis where | nulls become a separate type that has to be explicitly | declared in type signatures. | | So, yes. | xixixao wrote: | Both Flow and CoffeeScript got this right, while TS is slowly | dragging its feet towards the right solution: Pretend there's | no difference between them. Your code will be easier to | reason about. If you need two different "other values", use a | proper enum / type union / restructure your API. | bobbylarrybobby wrote: | I've always liked the two-nulls solution in JS. `undefined` | is a runtime-generated missing value, whereas `null` is a | compile-time author-supplied missing value. In other words | `undefined` is a "pulled" missing value, `null` a "pushed" | missing value. Any feature can be misused, but having the | distinction is certainly helpful. | mikeryan wrote: | I agree. I can understand the parent but I like these | distinctions and Typescript makes them easier to deal with | for me | lukifer wrote: | I'm fond of this distinction as well. One could also parse | it semantically as `undefined` meaning "unknown unknown" vs | `null` being a "known unknown" (or "this value left | intentionally blank"). | | Where I think it falls down in practice, is that JS still | treats undefined as a legitimate pseudo-value, as opposed | to a read-only return result for a missing key. So for | instance, `x=[0,1]` and `x=[0,1,undefined]` will both | return undefined for `x[2]`, and it takes jumping through | some hoops to know if that value was undefined on purpose, | or if the key is simply not found. | | If I had my druthers, attempting to set a value as | undefined would either throw a fatal error, or be an | alternate syntax to unset a value (such that `x.length` | would equal 2 in both examples above). | hn_throwaway_99 wrote: | > I have only seen null vs undefined lead to 2 things in my | experience: mistakes and bikeshedding. | | I disagree, though I think the implementation leaves | something to be desired. Primarily, I think there is | fundamentally a difference between the value of obj.bar in | the following examples that is useful to differentiate | between: | | { foo: 'hello' } | | { foo: 'hello', bar: null } | | For example, GraphQL makes specific use of this when dealing | with input types for mutations: null essentially means | "delete this field" while unset means "don't change it". | | There is a very good discussion on this topic here, | https://github.com/graphql/graphql-js/issues/133 , which goes | into the rationale behind it, how it's supported in languages | that do NOT differentiate between null and undefined, and how | some folks changed their minds on the issue. | goto11 wrote: | > Other languages refer to null as their billion dollar | mistake | | The "billon dollar mistake" as described by Tony Hoare was | not nulls per se. | | The billion dollar mistake was having a type system where | null was a member of _every_ reference type. This does not | apply to language like JavaScript without static type | checking, and it doesn 't apply to type systems like | TypeScript where null or undefined have to be explicitly | specified as members of a type. | | The undefined/null distinction solves an additional problem: | In Java you don't know if a value is null because a field | wasn't initialized correctly or because it was deliberately | set to null. JavaScript allows you to distinguish between | these two scenarios. | BillyTheKing wrote: | exactly.. the 'only' billion dollar mistake in JS in | regards to null is that typeof null === 'object' => true | colejohnson66 wrote: | It's not undefined, therefore it's an object. And because | every type can be null, it makes sense that it's just | "object", not "string" or whatever. | mpajunen wrote: | No need to use null for this, undefined works equally well with | noImplicitReturns. | | For example: | https://www.typescriptlang.org/play?#code/LAKAZgrgdgxgLgSwPZ... | cageface wrote: | I prefer to use undefined over null since it just fits more | naturally with other TS features like optional fields. But I | agree with using tsc's no implicit returns check. | amitport wrote: | You can declare Sometype|void return type. So the compiler will | check that you either don't use the return type or treat it as | Sometype. Of course this depends on the logic and for failed | routes you should return appropriate results. | zarzavat wrote: | void implies that the return type should is undefined | _behavior_ and should not be relied upon, so that something | like this const x = foo(); | | is incorrect when foo() returns void. 99% of the time x will | be undefined (the value) but there are cases where it would | not be. For example arr.forEach(x => | x.sort()) | | sort returns a value as well as having a side effect. But | forEach expects a _void_ callback. This code is perfectly | fine in JavaScript because forEach does not read the return | value of the callback. | amitport wrote: | I agree. I don't see where what you wrote contradicts what | I said. | IggleSniggle wrote: | Perhaps they were not trying to contradict you? | true_religion wrote: | I agree. You should type something as null if you _need_ to | force callers to deal with the null value and can 't do | that with an exception. | | Type it as void if the value isn't really important to the | caller, or you'll throw exceptions in an exceptional case. | | Common wisdom is to always have user-defined functions | return void, but sometimes I think it's okay to use void if | you're replacing a built in JavaScript functionality so the | outer code was relying on that semantic. For example, | replacing a simple usage of findIndex (that returned | undefined) with something more complex that does API calls. | e1g wrote: | +1, and this practice can be verified by TS with the | `noImplicitReturns` setting | (https://www.typescriptlang.org/tsconfig#noImplicitReturns) | cstrnt wrote: | Yeah, that rule is super powerful! | presentation wrote: | I actually don't really like Record types in the way | people/library maintainers often use them - the type-checker | asserts that values are actually present for all the specified | keys, which is fine if the objects with the Record type really | were exhaustive; but instead I often see them used where the | reality of the data is a Partial<Record> - some keys are missing. | Something about the abstraction causes people to misuse it | frequently. | e1g wrote: | Yep, and in your own code you can tell TS to verify your | property access with the setting | `noPropertyAccessFromIndexSignature` | (https://devblogs.microsoft.com/typescript/announcing- | typescr...) | | The TS crew did discuss having a "pedantic" mode, which is | stricter than "strict", but I don't think it's on the roadmap | any more. | bilalq wrote: | You pretty much always have to define your record type with `| | undefined` tacked on to the value type parameter. With that, | the problem mostly goes away. | lugged wrote: | What's the point of typing things if you have to constantly | typecheck them anyway? | milliams wrote: | I haven't used typescript much but it's surprising to me that you | can pass a `const` value to as an argument which is not | `ReadOnly`. Does `const` not really mean anything? | hajile wrote: | `const` means a constant pointer, but doesn't guarantee that | the data it points to will remain constant. | | In practice, functions, numbers, bigint, symbols, booleans, | strings, regex literals, null, and undefined are all immutable. | Since you can't change the value, a const to one of these | guarantees the value will never be modified. | | Objects, arrays (actually just objects with a different | constructor), maps, sets, TypedArrays (real arrays), etc are | different. You can be guaranteed that you will be pointing to | the same object instance because there's no way to swap out | data at a location in memory like there is in low-level | languages (yay GCs). The entries inside the hashmap or array | can be modified though. | | Calling `Object.freeze()` will lock down an array or object | with some caveats. Sub-objects will still be modifiable (though | you could recursively freeze) and this doesn't work for Map or | Set (their properties like get/set/forEach will be frozen, but | not the actual data) and it will throw if used on a typed | array. | keawade wrote: | That's a JavaScript specific nuance that typescript inherits. | | 'const' declares a variable with an immutable reference, not an | immutable value. If you're referencing a simple literal like a | string or a number that's effectively the same thing but for | objects (and arrays under the hood of JavaScript are fancy | objects) while the reference to your given object is constant, | the properties of that objects are still mutable. | shadowgovt wrote: | Correct. This is a thing you can do in JavaScript (and | TypeScript) that sometimes trips new people up but is exactly | what these keywords mean: const foo = []; | foo.push(1,2,3); | AtNightWeCode wrote: | Same in a lot of programming languages. A common source of | errors too. Maybe it is a bit more confusing in JS simply | cause people have been taught to make an active choice | between var, let and const. | Xenoamorphous wrote: | Same as Java's final, isn't it. You can use final when | declaring a reference to an object but that doesn't make the | object itself immutable, just the reference itself. | fenomas wrote: | "const foo = bar" just means that foo can't be reassigned to | some other value. It doesn't say anything about bar. | btbuildem wrote: | "any" is such a cop-out, it's a shame it exists. You can't eat | your cake and have it too. | sibeliuss wrote: | `any` is one of the main reasons why typescript is now so | popular. Its a dev-conversion tool. It's amazing for teams that | aren't ready to make the full leap. But later migration to | strict mode is necessary. | recursive wrote: | Then turn it off with your compiler options. Typescript never | would have gained significant adoption without it. It's | essential for gradual conversions from js codebases. | irrational wrote: | Does Typescript get better? I've been forced to use it for my | most recent project, but haven't been given any time to read | through the documentation. I've found that I'm spending about | 99.9999% of my development time trying to figure out how to get | my IDE to not show that their are typescript problem. At this | point I hate typescript with the burning fury of a trillion suns. | I wonder if this is everyone else's experience? | | Edit: So I take it from the downvotes that everyone else is a | genius and I'm the only one that has had a problem learning TS. | pbowyer wrote: | That was my experience too and yes, it gets better. I went from | "I don't understand it" to "I hate it with a passion and want a | proper typed language" to "uh okay it's caught 2 bugs I | overlooked. This is useful" during my first project. | | My advice: accept it's completely alien and is going to take | effort to learn. I found spending a weekend sitting down and | reading a theoretical guide was useful; there's good | recommendations further up this page. Understand there's | different levels of applying TS: one of its maintainers told me | to start off slow and use `any` wherever I didn't know what to | put. I pooh-poohed that because everything should be typed in a | typed language but he was right. I have a few `any`s and a few | `x as Type` where the code can't work it out because | something's gone wrong. It works and my next project will be | better. | | I still have no idea (and haven't found any tutorials) on how | to type form data (DTOs) or any object where stuff is | structured but can be optional or required. How do you validate | the type definition? What about HTTP responses which might have | data in multiple structures - how do you type each of these | depending on what's received? All answers welcome. | | I will get there. And so will you. Onwards! | | Aside: I got roundly mocked on a JavaScript framework's discord | asking questions about edge cases to help me build a mental | model of the language. Apparently it's "b.shit" and | "questionable" and "weird way to learn the language". Well... | now I know them I can infer what's going on, understand the | workarounds used and why things don't translate from other | languages. If that's the way you learn, embrace it. I still | think JS (and TS by extension) are weird in places. Neither | would be my choice of language, but I'm coming to appreciate | them. | patwoz wrote: | Yes, of course. But that's the case for all new languages. You | need to learn it. After some time you will be much faster in | programming with a typed language as in a untyped. | awestroke wrote: | Try fixing the problems | irrational wrote: | That is exactly what is taking 99.99+% of the development | time. Fixing the problems with typescript. My JS code has no | issues. | spoiler wrote: | Your original question of "does typescript get better?" can | be rephrased as "does any tool become easier to wield with | greater/better understanding?" and the answer is "yes". | | Without a [concrete] example of what type of issues you | faced, I doubt anyone can give you actionable advice, | though. | | > My JS code has no issues. | | Either the types were wrong, or the code was wrong, either | way there _is_ an issue. Since you said you 're | inexperienced with typescript, it's possible the types were | erong. | | There are certain JavaScript patterns where TypeScript can | be a bit inexpressive/unergonomic (especially with a lot of | "metaprogramming" or "runtime polymorphism"[1] involved). | | [1]: Most of the cases in our company the difficulty of | expressing some types of "runtime polymorphism" was what I | call thoughtless polymorphism (ie, it usually hides runtime | errors that you're unaware of, _especially_ if there 's a | proliferation of `anys`s in the code) | fidesomnes wrote: | if you don't understand objects and constructs you will hate | it. if you do you will love it. | handrous wrote: | My experience is that the worst part is the config file. Its | documentation isn't great and it's got a lot of cruft and | overlapping functionality that can be confusing. It's easy to | end up following a guide that's outdated or simply _wrong_ in | some subtle way that just happened to work for the original | author but was still incorrect, when it comes to the config | file. | | Once it's set up, though, it's wonderful. No more refreshing | only to discover you typo'd something or left out a step. You | can hand your code to someone else--or to your future self--and | they can often just start using it without having to ask | questions, read documentation, or read the code to figure out | what it does, because the types convey a ton of info and their | editor/IDE presents it to them as-needed. | | It lets you get your ideas about how the code works out "onto | the page", as it were, and in a format in which a machine can | automate most of the looking-up and finding-relevant- | information-for-this-context parts. It slows down _some_ code- | writing a little (mostly by making you document things that | should probably be documented anyway, either with tests [yes, | tests are documentation] or in a manual or whatever) but speeds | up _using_ code so much that it more than makes up for the | cost. | | It's an _incredible_ communication tool. | | If you rarely need to communicate with others _or with your | future self_ about code you 're writing, then it may not be | worth using. So, if you're on a smallish solo project that you | don't intend to stop working on until you're done working on it | _forever_ , never plan to hand to anyone else, and that you | spend so much time working on that the whole thing's in your | head nearly all the time, it might be fine to just write JS. | spaetzleesser wrote: | As mainly C# programmer I am getting quite jealous of TypeScript. | Seems there is a lot of really interesting stuff going on there. | ryanmarsh wrote: | What can be done with index access really blew my mind. | | https://www.typescriptlang.org/docs/handbook/2/indexed-acces... | EnderShadow8 wrote: | Note: don't use typeof x === 'object' to check whether something | is a valid object, because it will return true for arrays as | well. | | Arrays are objects, so this is expected behaviour. | WA wrote: | Note: please post a better solution. | dgb23 wrote: | I very, very much disagree with the type of suggestions here. | Probably because I don't know TS but here it comes anyways... | | You don't need a library or check that it isn't an array or | anything like that. What you actually want to check is what | it _is_. | | You absolutely don't care whether that object is an array or | a function or what have you. What you care about in that | piece of code is that it satisfies what you want to do with | it. | | In the context of the article you can do an ad-hoc check on | the fields that you require in that context. | | Aside: | | There are places where you want to have a kind of rigidity | around the shape of your objects, including homogeneous | collections. The JIT might reward you with optimizations in | certain cases. But that is optimization, so you are supposed | to measure first and only then apply them or have a very | clear picture of how your runtime behavior will be. | Fckd wrote: | x && !Array.isArray(x) && typeof x === 'object'. | | its typical to start off with `var &&` to avoid operations on | null & undefined values | Fckd wrote: | For environment lacks Array.isArray method i think this | works well | | !!(x && x.__proto__ === [].__proto__) | [deleted] | nsonha wrote: | https://javascriptweblog.wordpress.com/2011/08/08/fixing- | the... | | tl;dr: Object.prototype.toString.call, you most likely to | also want to exclude null, RegExp, Function, Date, Number, | Boolean & String (not string) | presentation wrote: | Probably best to just lift one off of a major library like | Lodash, they're well tested and efficient (no need to | actually use the library, do include the LICENSE somewhere | though): | | https://github.com/lodash/lodash/blob/master/isObject.js | | But depends on your needs, and you can also attach a | typeguard to it. | chucky wrote: | Why not use the library? With tree shaking and/or direct | imports you will ensure the same bundle size as if you just | copied the file, and you don't have to worry about licenses | etc. In fact, since other dependencies might depend on | lodash you can deduplicate the import and actually save on | bundle size. | | You'll also get notified of any security issues in your | lodash imports if your CI pipeline is setup for doing that | kind of thing. | [deleted] | nicoburns wrote: | If you want to avoid a library then you can do `typeof x === | "object" && !Array.isArray(x) && x !== null`. Not ideal, but | you can of course turn it into a utility function. | otrahuevada wrote: | a simple solution would be | | > if(entry && entry.constructor === Object){} | jwalton wrote: | This isn't a great solution: entry = | Object.create(null); entry.name = "Jason"; | entry.age = 42; entry.constructor === Object // | false - constructor is undefined. | | `entry` is a valid Human here, but fails your check. | Actually, just creating a new class that implements the | Human interface will cause a similar problem, since the | constructor will be the class instead of Object. You don't | even _really_ want to exclude arrays here: | entry = [] entry['name'] = "Jason"; | entry['age'] = 42; | | Again, entry is a valid Human here. | otrahuevada wrote: | I'm having some trouble finding your proposal in the | thread... how would you do it? | jwalton wrote: | :) Fair enough: function isHuman(obj: | unknown): obj is Human { return !!obj && | typeof obj === 'object' && typeof (obj as | any).name === 'string' && typeof (obj as | any).age === 'number'; } | | This checks the shape of the object, and returns true if | it's a `Human`. This will work for array or objects or | classes or anything. | cstrnt wrote: | You're absolutely right. Should have stated that I meant a | plain object (key-value-pair). But either didn't want to focus | on this topic for that post because it would have been just a | bit too much to talk about :D | hardwaresofton wrote: | Array.isArray[0] is your friend | | [0]: https://developer.mozilla.org/en- | US/docs/Web/JavaScript/Refe... | IggleSniggle wrote: | Did anybody else have their brain do a weird backflip seeing | `Array.isArray[0]`?? | Object.getOwnPropertyDescriptors(Array.isArray) /* | { '0': { value: | 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Refe | rence/Global_Objects/Array/isArray', writable: | true, enumerable: true, configurable: | true }, length: { value: 1, writable: false, | enumerable: false, configurable: true }, name: { | value: 'isArray', writable: false, | enumerable: false, configurable: true } | } */ | threatofrain wrote: | How about | | any => Object.prototype.toString.call(any).slice(8, -1) | nsonha wrote: | bit more self-explanatory `it => | Object.prototype.toString.call(it).match(/^\\[object | (\w+)]/).pop()` | nsonha wrote: | or even this | | typeName = Object.prototype.toString.call | JoBrad wrote: | I really like this package, because it not only uses the | method you mentioned for checking types, but is written in | TS, so you get the type checking feedback. | | https://github.com/sindresorhus/is | [deleted] | Etheryte wrote: | The core problem here is that you don't actually want to check | if something is an object, but whether it matches the Human | type. The correct way to do that is to define a type guard [0], | for example: function isHuman(input: any): | input is Human { return ( Boolean(input) && | Object.prototype.hasOwnProperty.call(input, "name") && | Object.prototype.hasOwnProperty.call(input, "age") ); | } | | There are libraries which can automate this for you which is | the route I would recommend if you need to do this often. As | you can see, the code to cover all edge cases such as | `Object.create(null)` etc is not trivial. | | [0] https://www.typescriptlang.org/docs/handbook/advanced- | types.... | wdfx wrote: | Your input parameter type should be unknown, not any. | Etheryte wrote: | That's fairly subjective and I can see arguments for both | sides, in the above example I've gone with the approach the | Typescript documentation itself gives which uses any. Given | the parameter is only used as an input, using any and | unknown are interchangeable and there is no difference in | this specific case. | wdfx wrote: | I somewhat agree, but to me the semantics using unknown | makes more sense as the function is attempting to answer | a question of the input object. | | "What is object? I don't know. Does it meet these | conditions? Yes then object is Human else not." | Etheryte wrote: | While I see where you're coming from, semantic | formulation like this is highly subjective. All the same | the problem statement could be "Given any kind of input, | tell me whether it's a Human or not". | mikojan wrote: | This approach is "correct" only when you're interacting with | an API you have no control over whatsoever because specifying | all this and maintaining it is really error prone. | | All this boilerplate is not a proof. It remains an assertion. | And so in most cases you might as well just add a | "type"/"kind" property to your object (or create a class). | pbowyer wrote: | > There are libraries which can automate this for you which | is the route I would recommend | | Which libraries do you recommend? I've had to do this a | couple of times in my current project and it's painful (and I | made mistakes). | Etheryte wrote: | Personally I really enjoy Typanion [0] since it's very | similar to Yup [1] which I previously had extensive | experience with. You can find more alternatives and a | lengthy discussion about the whole problem space and its | history in [2]. | | [0] https://github.com/arcanis/typanion | | [1] https://github.com/jquense/yup | | [2] https://github.com/microsoft/TypeScript/issues/3628 | conaclos wrote: | And it will return true for null as well ;) | 1penny42cents wrote: | This is a nitpick; but the first example doesn't make sense once | we use ReadOnly, because it doesn't return the copied+sorted | array. So in practice the final version of sortNumbers would have | no effect afaik. | genezeta wrote: | The final version of _sortNumbers_ is: | function sortNumbers(array: Readonly<Array<number>>) { | return [...array].sort((a, b) => a - b) } | | And _sort_ sorts in place and then returns the sorted array[0]. | So here the newly created _[...array]_ is sorted and then | returned by _sort_ and then by the _return_ at the start of | that line. | | [0] https://developer.mozilla.org/en- | US/docs/Web/JavaScript/Refe... | spicybright wrote: | I'm going to be one of those people, but multiple "=" rendering | as a large double line is really ugly. | bmn__ wrote: | You don't have to suffer (subjectively) bad typography. | Cascading style sheets were designed with the ability to | override author styles with user styles, your Web browser has | settings for this. | spicybright wrote: | Of course, I'm not going to muck around with brittle webpages | that can't take a webpage just to fix one blog post, though. | recursive wrote: | I totally agree about ligatures. My user style sheet works | pretty flawlessly for everything. * { | font-variant-ligatures: none !important; } | | I cannot comprehend how ligatures ever got popular in | programming, particularly in blogs supposedly trying to | teach new languages or concepts. | spoiler wrote: | For me personally, I found that they help reduce the | noise in the code. | | I also noticed that it makes it a bit easier to "read" | the code (not just visually, but "semantically" if that | makes sense). As in, I think I have to spend less time | "parsing" <= than =<, but I don't have a way of really | "proving" it. | | However, I am mildly dyslexic, so that might play a role | in it. | exdsq wrote: | Still working on this, but might give someone a laugh :) Problem | 1 from Project Euler in TypeScripts Types | | https://github.com/iiTzEddyGG/typing-euler/blob/master/src/p... | chana_masala wrote: | I am highly interested in type programming lately, exactly the | kind of thing you're doing here. Do you have any other | resources that inspired you? Here are some of my favorites: | | https://gist.github.com/hediet/63f4844acf5ac330804801084f87a... | | https://github.com/codemix/ts-sql | | https://github.com/jamiebuilds/json-parser-in-typescript-ver... | | https://gist.github.com/acutmore/9d2ce837f019608f26ff54e0b1c... | exdsq wrote: | I really like Type Driven Development in Idris but that's a | little more serious (it's things you can do in production!). | But my all time favourite writing ever is | https://aphyr.com/posts/342-typing-the-technical-interview | marton78 wrote: | Kinda off topic from someone whos mother tongue is not English: | what happened in the past years that people apparently forgot how | the irrealis works in English? Shouldn't this be "Things I wish I | had known when I learned TS" as opposed to "Things I wish I knew | RIGHT NOW"? | pavlov wrote: | You're not wrong, but language evolves. What's convenient in | the mouth of native speakers today will usually become | grammatically correct eventually. | | It would be neat to title blog posts in mock Elizabethan | English though: | | "Miscellania of Out-Most Significance to the Young Man Who | Desireth to Learn the Merveillous Type-Scripte" | nosefrog wrote: | As a native English speaker, I never learned formally about | irrealis, so it's not possible for me to have forgotten them :P | "Things I wish I had known" and "Things I wish I knew" both | sound right to me. | cunningfatalist wrote: | Nice article, I like it. I can recommend "Programming TypeScript" | by Boris Cherny (O'Reilly, 2019) and "Effective TypeScript" by | Dan Vanderkam (O'Reilly, 2019), if you want to learn more like | this. | porker wrote: | And https://exploringjs.com/tackling-ts/ | throwanem wrote: | The third example describes something useful in record types, but | goes about it in what seems an odd way, and ends up suboptimal as | a result. I'd instead use an object type like this: | type Human = { name: string; age: number; | } | | which also enforces value types in the compiler, rather than | requiring runtime guards. | mikeryan wrote: | I think the example is a bit contrived with a preset union. | Where it's really valuable is when you're extracting a union | from another source (like via keyof) and want to keep the two | objects in sync without having to modify the keys in two | places. | _fat_santa wrote: | I found that pattern useful when you don't know what the key | will be. I have an iOS app that tracks tips, when you add a | tip, it's stored in an object like this: | | type Tips = { [tipGuid: string]: TipObject } | | which can be rewritten using Record as | | type Tips = Record<string, TipObject> | | That pattern ins't very useful when creating object with known | keys but for data structures where the key is either not known | or generated it a godsend. | phailhaus wrote: | `Record` is a dangerous type, and I would recommend against | it. The problem is that it assumes _any_ key is valid and | will return a value type. Example: type | Tips = Record<string, TipObject> const tips: Tips = | {} tips["hello"] // TipObject, but you actually get | undefined | | It's better to define a Dictionary type like so: | type Dictionary<K extends string | number | Symbol, V> = | Partial<Record<K, V>> | | Then to use: type Tips = Dictionary<string, | TipObject> const tips: Tips = {} | tips["hello"] // TipObject | undefined | | That way, you're always forced to check for existence, and | you never accidentally attempt to access properties on | `undefined`. | jakelazaroff wrote: | It's not true that Record will result in a type where any | key is valid. If you pass in a primitive like string, then | of course any string will be valid. That's not Record's | fault; what you're doing is essentially creating an index | signature [1]. If you pass a more restrictive type in as | the key, it works as expected: type Tips | = Record<"foo", TipObject>; const tips: Tips = {}; | // error, needs key "foo" tips["foo"]; // fine | tips["bar"]; // error, no key "bar" in tips | | It's worth mentioning that this isn't just an issue with | objects. For example, by default, the index type on arrays | is unsafe: const arr: number[] = []; | const first: number = arr[0]; // actually undefined, but | typescript allows it | | If you _do_ need an index type and want to account for | undefined keys, the idiomatic way is the | noUncheckedIndexAccess compiler flag [2], which will | automatically make any index property access a union with | undefined. | | [1] https://www.typescriptlang.org/docs/handbook/2/objects. | html#... | | [2] https://www.typescriptlang.org/tsconfig#noUncheckedInde | xedAc... | [deleted] | [deleted] | throwaway284534 wrote: | You're right that this is really easy to mess up, | especially when defining an array index e.g. | const todos: string[] = ["walk dog"]; | todos[123].toUpperCase() // error! | | IMO non constant (as defined by TypeScript) arrays | should've been automatically assigned a union type with | `undefined`, which can also be a fix for Records too: | type Tips = Record<string, TipObject | undefined> | jakelazaroff wrote: | You're looking for the noUncheckedIndexAccess compiler | option: https://www.typescriptlang.org/tsconfig#noUncheck | edIndexedAc... | spoiler wrote: | While this is probably alright for some data, I'd definitely | recommend using something like a Map instead (especially if | the object mutates) for things you have control over (ie it's | not describing an endpoint or something similar). | OJFord wrote: | Which is exactly what _was_ used in the code exemplifying | something else in the _second_ example - so that confused me | (never having written any TS) too. | throwanem wrote: | Yeah, typically you want a record type for something like a | mapping over potentially arbitrary keys (via an index type) | to values of known type, and an object type (which can have | optional keys) when you _do_ know exactly what shape you | expect and want to enforce it. | wdfx wrote: | The type itself is fine but how it is checked and used is not. | The following would be better, and in this case you will also | have a Human type inside the forEach callback: | // ... other code type Human = { name: string; | age: number } const isHuman = (obj: unknown): obj | is Human => obj && typeof obj === 'object' && 'name' in obj && | 'age' in obj; // you can complete the gaps here and also check | the property types | someArray.filter(isHuman).forEach((h) => { // h has | type Human now console.log(h.age); }) | throwanem wrote: | This is reasonable for validating an untrusted object, sure, | but I don't recall that constraint being expressed as part of | what the original post was addressing. | 3np wrote: | I think the example type is useful for unvalidated input data | e.g. from an API call. Or an update function for a DB | abstraction. | | Internally in the backend, you'd still use the type you just | posited. | throwanem wrote: | Eh. A good ORM provides type definitions from model | definitions, which is one way I've found ORMs more useful in | TS than JS, and I'd more likely use a runtype or a decoder to | both validate and type inbound data than roll my own | interface for it. | | On review of documentation, I was actually pretty off base in | grandparent comment. The real use case for Record appears to | be when you need a map type whose keys are both explicitly | enumerated and defined elsewhere, ie in a union, enum, or | otherwise unrelated object type. Rather than duplicating the | keys, you can use Record<someUnion, V> or Record<keyof typeof | someEnum, V> and only have to make one change to update both. | | For the "arbitrary keys, known value types" case I mentioned | earlier, an object type with an index signature works fine | and may be more legible. | chana_masala wrote: | > an object type with an index signature works fine and may | be more legible. | | If I understand you correctly, that's exactly what a Record | is underneath | throwanem wrote: | More or less. There are extra steps involved with a | Record, but they work the same iirc. | charesjrdan wrote: | Is there ever a reason to use interface over type? From what I've | seen it looks like they can both do the same thing but with | slight differences in syntax | shadowgovt wrote: | They are very similar, but they are subtly different in ways | that might matter, depending on what you want to do. | | A type is statically-"tagged" data from the typechecker's point | of view. Even if `Foo` and `Bar` are two types with the exact | same fields, the typechecker won't let you use as Foo as a Bar | or vise-versa unless you've explicitly declared that Foos are | Bars (via type aliasing or inheritance). | | An interface declares a whole category of types that are | equivalent: anything with the same "shape" as the interface | will count as the interface. So you can pass objects, child | objects, objects with additional fields attached, etc. to an | interface input; if the thing has the fields the interface | cares about, it'll accept it. | | Which you want to use depends on what precisely you intend to | do, but interfaces are handy in TypeScript where they may be | less useful in some other languages because the underlying | JavaScript is so "duck-typed" and sloppy on what it means for | something to "have a type;" interfaces often model more | accurately the behavior of "native" JavaScript functions (that | will take an argument, assume it's an object, and just start | touching some fields on it without caring whether more fields | exist or not). | csnweb wrote: | Yes interfaces can be merged: | https://www.typescriptlang.org/docs/handbook/declaration- | mer..., which can be useful when working with external | libraries. | henriks wrote: | One detail I stumbled upon the other day is that interfaces can | use the `this` keyword to refer to the type implementing the | interface. This isn't supported for types afaik. | | https://www.typescriptlang.org/docs/handbook/advanced-types.... | iaml wrote: | I prefer types over interfaces because of one simple | difference: if you use vs code and hover over type alias, it | expands it and shows everything inside, while for interfaces it | just shows the name. | conaclos wrote: | In my knowledge, there are some differences: | | - in error reporting: type aliases may be replaced by their | definition in error reporting. | | - you cannot create union types with interfaces | | - legacy versions of TypeScript does not enable to create | recursive type aliases such as type `List<V> = {v: V, right: | List<V> | undefined }` | | - interfaces with same name are merged | | However the frontier between type aliases and interfaces seems | more and more blur. Generally interfaces are encouraged over | type aliases. I personally prefer type aliases because there | are more capable and seems more elegant to me. However their | poor support in error reporting makes me rely more on | interfaces when possible. | arenaninja wrote: | Yes the error reporting is why I prefer interfaces over types | since a long time now. The definition is useless in many | cases | kkirsche wrote: | "Type aliases and interfaces are very similar, and in many | cases you can choose between them freely. Almost all features | of an interface are available in type, the key distinction is | that a type cannot be re-opened to add new properties vs an | interface which is always extendable." ... | | "For the most part, you can choose based on personal | preference, and TypeScript will tell you if it needs something | to be the other kind of declaration. If you would like a | heuristic, use interface until you need to use features from | type." https://www.typescriptlang.org/docs/handbook/2/everyday- | type... | pietrovismara wrote: | You still can extend types with type intersections: | | type A = { id: number } | | type B = A & { name: string } | | const b: B = { id: 0, name: 'foo' } | moron4hire wrote: | That's creating a new type B. In contrast, interfaces can | have their definition spread across multiple code units. | interface X { x(): void; } | interface X { y(): void; } | class Y implements X { x(): void { | console.log("hello"); } y(): | void { console.log("world"); } | } const z = new Y(); z.x(); | z.y(); | | This is important for keeping up with API changes in | browsers that may happen faster than the DefinitelyTyped | project can keep up. | [deleted] | DavidMankin wrote: | Effective TypeScript [0] has many more well written and explained | tips to know to use TypeScript well. I highly recommend it. | | [0] https://smile.amazon.com/Effective-TypeScript-Specific- | Ways-... | lifthrasiir wrote: | interface Person { [key: AllowedKeys]: unknown | } | | This is one of annoying aspects of TypeScript. The following | should work (in fact, this is how `Record<K, V>` is defined in | lib.es5.d.ts after all): type Person = { | [key in AllowedKeys]: unknown }; | | Note the switch from interface to type and `:` replaced with | `in`. The point is that the `in` syntax (conceptually expanded | into multiple fields) subsumes the `:` syntax (a generic type | ascription) and is only available as a mapped type, which is | distinct with an interface type. The error message does mention | this, but if you don't know what is the mapped type you are left | with no clues. | mwhnrt wrote: | I like your tone! | cstrnt wrote: | Thank you very much :) | tus89 wrote: | > Well, arrays and objects are quite special in JavaScript. If | you pass them to a function it will pass the reference to the | array or object which means it will mutate the original array | | Unlike every other language that passes arrays by value. Ye gad. | nathias wrote: | Everything that TS does JS libraries do better without the | horrible tradeoffs ... sadly MS has invested so much into | promoting it that it's now almost a requirement for all software | development. | handrous wrote: | > Everything that TS does JS libraries do better | | Which ones do you need to do everything TS does? | | > without the horrible tradeoffs | | Which tradeoffs are horrible? | nathias wrote: | For example if you want prop types you use propTypes. In | React TS replaces good error handling for horrible obscure | errors and slows down development considerably etc. etc. | shadowgovt wrote: | propTypes does runtime type checking, which is a different | kettle of fish from static type checking. | | The advantage to static type checking is that it removes | the performance cost of runtime type checking where it's | unnecessary; the language's rules make it impossible to | build some constructs where the wrong types get mashed | together. The tradeoff is that you have to code so the | wrong types don't get mashed together (which is, arguably, | your goal in the first place). | | You can do everything a statically-typed language does in a | non-statically-typed language via best practices, but | that's a bit like saying you can do everything a compiled | language does in assembly via emulating what the compiler | would output. In theory, the compiler is saving you the | headache of doing that (but depending on the size of what | you're trying to write, sometimes it _is_ simpler to write | it in JavaScript and skip the type safety. That code is | harder to grow, but not all code grows!). | nathias wrote: | So the whole baroque arhitecture is there to 'remove | performance costs'? That's an even worse reason to use TS | than avoiding prop type bugs. | shadowgovt wrote: | This is a good response to drill down on, because the | poster's assessment of the purpose is accurate. | | Yes, much of both TypeScript and React are there to | minimize performance costs and improve software | reliability in tens-of-thousands-of-lines-of-code | projects: TypeScript is using static type safety to | replace the need for dynamic typechecking (decreasing the | expected runtime error rate and the runtime cost of | dynamic type analysis; static typing tells you both when | you _must_ runtime-coerce types and when such coercion is | unnecessary and would waste performance). React is using | delta-detection of lighter-weight objects to determine | when heavier-weight objects in a declarative user | interface API need to be changed, impacting performance | (because a handful of equality compares against objects | or plain data is a fraction of the cost of repainting | every pixel in a table with pixels representing the exact | same information as before to the end user). | | These are problems people face, but if they are not the | problems you're facing, they might not be the tools you | need. Not everyone is writing the Facebook UI. There are | lighter-weight tools out there that solve similar | problems with less complexity (the tradeoff, perhaps, | being that if you _do_ find your software needing to | scale to handle updates to represent complex, | heterogeneous data or infinite streams of information, | those tools might not scale easily... But how many people | _actually_ have that problem?). | | "Use the right tool for the job" is one of the | cornerstones of the art of software engineering. | nathias wrote: | Yes, the right tool for the job, but because TS covers a | set of tools its rare that this is exactly the tool you | need. I'm sure there are cases but mostly I think people | just use it because of the hype/preference and I really | hate to make prototypes with TS. | shadowgovt wrote: | Yeah, prototypes are probably the wrong use case for | React or TypeScript because they can crash without | causing someone $X million in revenue (by definition; if | it _can_ cost someone $X million in revenue, it 's no | longer a prototype and the team maintaining it probably | wants stronger guarantees than what native JavaScript | provides for proper use of APIs and data handling if they | ever want to sleep at night). | iaml wrote: | I'm really glad proptypes are not used anymore, ts is | simply better. It's more flexible, gives stronger | guarantees in some cases (PropTypes.func does zero checks | for signature, for example), better support in editors, | better integration with other libraries, allows typing | hooks/context. If you need runtime checks, use io-ts or | runtypes. | errantspark wrote: | Not OP but I do tend to avoid TS. I don't like the additional | friction of working with the language (transpiling, unable to | copy/paste directly into an interpreter). I also feel like | the community at large writes awful baroque code that makes | me want to die. Why use a function when 18 classes | subclassing eachother across 4 files will do? If you're | familiar with the tiktoker @khaby.lame, TS feels like exactly | the over-complicated life hacks he mocks. | zkldi wrote: | You can use `ts-node` to copy paste directly into an | interpreter. (`npm/pnpm install -g ts-node`) | errantspark wrote: | Fair, I should have been more clear when I said | "interpreter" what I really meant is "the chrome | debugger" which is one of JS/Node's absolute killer | features. I believe support there is on it's way, though | I very much doubt it'll convince me of TS' value. | | I used to be much more bullish on TS, I like the idea of | strong typing in general but the more I use TS the more I | feel like it's just the worst of both worlds. I much | prefer my types to be deeply embedded in the language | design, types as varnish don't make sense to me anymore. | stnmtn wrote: | You can very easily use chrome debugger on sourcemapped | TS files | errantspark wrote: | This is news to me, as far as I know it'll error out when | it reads TS on the REPL. How do you enable this? | stnmtn wrote: | Oh, in the console? Probably won't work? I'm saying that | you can run chrome's debugger on source mapped TS files, | which is really really nice for bug-hunting and | developing locally | | But I mean if the biggest complaint you have is that it | doesn't run in a browsers REPL console, that feels pretty | minor to me. You can easily find TS REPLs all over the | web | | Edit: if you are asking about chrome debugger, here's a | link to some info | https://stackoverflow.com/questions/43627243/using- | chrome-to... | errantspark wrote: | Right, but the actual benefit comes from having the REPL | and the debugger in the same place. Plus the chrome REPL | is fantastic, so suggesting I just use a different one is | sorta like saying "you can just use your phone" when my | default is a Hasselblad. When I'm writing JS/Node I can | write code in the REPL in the context within which it | will execute, it really cuts down on how much I have to | hold in my head at one time because I can just ask the | computer. It also makes exploring much much faster | because my iteration times are as fast as the computer | can run my code. I find it's a much more natural way of | programming and about as close to SLIME as I can get | while still writing in a language that has easy economic | value. | stnmtn wrote: | Not sure if this solves 100% of your problems, but this | seems like it could help a bit | https://chrome.google.com/webstore/detail/typescript- | console... | | Also for me, the benefits of a statically typed language | on a large team heavily outweighs not having TS in a | chrome REPL. It's not even close. Maybe your use case is | different, but for me it seems like you're missing the | forest for the trees. | errantspark wrote: | > benefits of a statically typed language on a large team | | I mean yeah, sure, if you have to work on a large team in | the browser/node you're going to have to make tradeoffs | for that, and using TS seems like it'll help everyone go | home at 5. I don't think I'm missing the forest for the | trees, we're talking past each-other. | | To illustrate a bit further, while I like Rust a lot, | (the problems it tackles are MUCH more real than the ones | TS does) and I put in the effort to learn and use it on a | few personal projects I still find myself reaching for C | almost universally these days. Even in the case of Rust's | very useful tradeoffs I feel like they cost too much of | my freedom, and TS' guarantees are much more surface | level for a similar cost. | nathias wrote: | Exactly, this plus adding a new layer of complexity and a | new corpo sponsored replacement for something that isn't | broken ... | stnmtn wrote: | No one using TS claims JS is broken. People want a good | type system in javascript, and with that obviously comes | a new layer of complexity | | The tradeoff is clearly worth it for certain use cases, | and clearly not worth it for others. It seems you are | discounting and ignoring cases when it is worth it. | nathias wrote: | I don't think there are no use cases, but I think they | are rarer than people think, and that for the most of | development TS will be picked because it's someone | preference not because of its actual benefits. | stnmtn wrote: | Do you really think that use cases for statically typed | languages are rare? | errantspark wrote: | TS =/= statically typed languages | | Static typing can be a very useful tool especially when | the language is designed around it, in TS it is a painful | kludge. | yesbabyyes wrote: | I've found the perfect middleground is typescript checking | with JSDoc syntax. The code is just JS, no emitting | necessary, but you get all the type checking. | | Together with testing of @example stanzas, you get | everything for cheap-ish. | | Alas, there is no good JSDoc @example test runner1. This | would be very valuable. If there were, I would let that | handle my unit tests and focus only on integration testing. | | Edit: runtime type checking at the boundary is also | valuable! I've tried runtypes, but actually prefer | compiling the ts definitions to JSON Schema with | typescript-json-schema, and checking with plain JSON Schema | validation. | | 1I've used @supabase/doctest-js and jsdoctest, and found | both lacking. If you know of a better one, please share! | alde wrote: | TS !== crazy OOP. For some reason, there are people who | just want to use Java style OOP in TS. Nest.js is one | library heavily promoting that. I can't digest such | codebases, they are the definition of over-engineering to | me. Fortunately, out of dozens of medium to big TS projects | I worked on, only one used that style. In all the other | projects there were very few if any classes. | skywal_l wrote: | Utility Types[0] will help you get to the next level on | Typescript. It's important to know them and know how and when to | use them. | | [0] https://www.typescriptlang.org/docs/handbook/utility- | types.h... | mithusingh32 wrote: | Wow, thanks for this. I wasn't even aware of these. | | I'm surprised I rarely see these in courses/tutorial. These | should be like day 1 material. | skywal_l wrote: | Typescript is actually a great language. And with those | utility types, you can do pretty fun stuff like, for example, | you want to mutate a type so that some fields become | mandatory: type Ensure<T, K extends keyof | T> = T & { [U in keyof Pick<T, K>]-?: T[U] }; | class A { foo?: number; bar?: number; | baz?: number; } type MandatoryFields = | "foo" | "baz"; type B = Ensure<A, | MandatoryFields>; const b: B = { foo: 42 }; | | Here, ts will complain that b is missing baz. | ketzo wrote: | Ok, the comments that this is a little hard to read are | fair... but this is _really_ cool. Thanks for sharing. | CuriouslyC wrote: | Why write code like that, instead of extending the class | with a mandatory property? The above code is going to be | inscrutable to a lot of engineers, and this isn't something | like an ORM where there's a good reason for that. | phailhaus wrote: | A better example is `Partial`, which makes all properties | on an interface optional. Lots of use cases for that, | like creating a `Dictionary` type that forces you to | check for undefined values, or allowing you to support | partial-updates to types without having to repeat your | interfaces. | | The other thing is that types are more often used than | read. You don't need to read the `MandatoryFields` type | definition often, because your IDE/typechecker will | automatically enforce the contract and tell you when | you're missing properties. | kilburn wrote: | The advantage here is that you are not repeating the type | of the property twice (once as optional in the base type, | once as mandatory in the extended class). | | Even though the code may seem inscrutable, note that the | resulting type is fairly easy to understand in your IDE. | That is, if you hover over the "B" to see what the type | definition is, you see: type B = A & { | foo: number; baz: number; } | | If you defined the type like this (which is equivalent to | extending the class as you were proposing) and later on | someone changes the type of one such mandatory properties | in the base and/or extended class without changing the | other, the error becomes much much weird, on the lines | of: | | > Type 'number' is not assignable to type 'never'.(2322) | | Here typescript is saying that a prop cannot have a value | (type never) because the base class defines it as | "number?" but the extended one defines it as "string", | and the intersection between them is empty. This is hard | to understand when it pops out where you don't expect it. | Harder than ignoring the weird "Ensure" thing, seeing | what it does (the resulting type B definition) and moving | on. | | Defining advanced types may be cumbersome, but dealing | with code that uses them is still approachable. This | allows the more experienced team members to "shape the | ground" and less experienced members still reap the | benefits even if they don't fully understand how the | thing works. | nkingsy wrote: | I'm building a strongly typed form abstraction layer for | work. I use code like this to express "if this generic | can be undefined, this field is required. Otherwise it | cannot be used". | | So: FormElement<string|undefined> needs to have a | "disabled" function, indicating conditions under which it | becomes disabled (and absent from the model), while | FormElement<string> must not have a disabled function, as | it will always be present in the model. | | One pitfall of this approach is it requires a lot of | trial and error to find the incantation that both works | and doesn't swallow error messages. | srcreigh wrote: | TIL, interfaces can extend classes in TypeScript. [0] If | interfaces could not extend classes, that would be a | reason to use type programming. | | Another reason could be a generic interface. If you have | a lifecycle where a type is mutable at one point but | immutable at later points, you could use mapped types to | enforce those constraints on the class methods | generically. | | [0]: https://www.typescriptlang.org/play?#code/MYGwhgzhAE | AKCmAnCB... | goto11 wrote: | Utility types are useful for example in the React API. | You have a "state" defined as a set of properties. Then | you have a setState() method where you return the set of | properties you want to update, which may be a subset of | the full state. So if the type of the component state is | TState, then the return type of setState() can be defined | as Partial<TState>. | dllthomas wrote: | I don't have it handy but with template literal types I was | able to have a type of "stripped strings" (that is, strings | without leading or trailing whitespace) that seemed | surprisingly usable - string literals would match (or not, | as appropriate) with no boilerplate, while dynamic strings | would need to be fed to a cleaning function. | | I never put it in production, partially because of concerns | over maintainability but far more because I had no need for | it. | yulaow wrote: | This seems extremely hard to read for me, I would have an | hard time trying to understand what it does if I found it | in any source code | handrous wrote: | Me, reading the link at the top of this thread: oh wow, | those are really cool. | | Me, reading this example: oh no, those all need to be | added to our project's lint rules to make sure no-one | uses them. | chana_masala wrote: | Why would you prevent their usage? They're incredibly | helpful | handrous wrote: | I guess it'd be OK as long as everyone always accompanied | such lines with a comment, with at least one line per | symbol or character it's identifying & explaining. As all | but the most trivial regexes warrant. | skywal_l wrote: | type Ensure | | I define a type called Ensure <T, K | extends keyof T> | | This type takes two type parameters, one called T and the | other K which will consist of Keys belonging to the type | T (in our case, "foo", "bar" or "baz"). | = T & | | This new type (called Ensure) will be equal to the union | of two types: One will be T and the other will be: | { [U in keyof Pick<T, K>] | | A new type which keys will be picked among the key listed | in K -? | | To which we will remove the potential optional qualifier | : T[U] }; | | And which types will be the same as in T. | jgwil2 wrote: | > This new type (called Ensure) will be equal to the | union of two types | | You mean intersection, right? | | EDIT: link to docs on intersection types: https://www.typ | escriptlang.org/docs/handbook/2/objects.html#... | alpha_squared wrote: | This feels like considerable cognitive load for any | developer that needs to work in more than one language. | jorisd wrote: | Most of the implementation details here don't really | matter until you need to modify these advanced types | directly. That Ensure type definition line in that | example is a low level detail that you put in a library | somewhere, import throughout your codebase, and then | mostly forget about. | | In practice you'd have someone that understands this set | it up once, and then document its usage for others, maybe | document the implementation to make it easier to modify | later. | | The TS compiler is surprisingly good at giving you good | readable error messages as well when your code violates | these advanced types; the errors tell you what you | specified and what is supported, it doesn't display the | low level type logic as part of the error users see. This | means that there's very little need for anyone to really | how these type definitions work. | | EDIT: clarifications and spelling. | pavel_lishin wrote: | > In practice you'd have someone that understands this | set it up once, and then document its usage for others, | maybe document the implementation to make it easier to | modify later. | | In practice, that someone then leaves the company, | leaving this nightmare underfoot. | | > The TS compiler is surprisingly good at giving you good | readable error messages as well when your code violates | these advanced types | | Only if you're that original person who understands it! I | would still have no idea what is happening, no matter how | clear. | jorisd wrote: | The sample type code mentioned above will give the | following error on the TypeScript Playground | (https://www.typescriptlang.org/play): | "Type '{ foo: number; }' is not assignable to type 'B'. | Property 'baz' is missing in type '{ foo: number; }' but | required in type '{ foo: number; baz: number; }'." | | I've had the compiler emit errors much like the following | for way more complicated types that combined several of | these kinds of structures together to form much more | bespoke type checks (reproduced from memory, so I'm not | 100% certain on the error or use case): | const e = form.email ^ ERROR: "email" is not | in '"name" | "firstname" | "lastname" | "e-mail" | | "birthdate" | "password"' | | The thing to note here is that it often doesn't expose | the details of the implementing type and underlying | (admittedly complicated) type system primitives at all to | users. That said, I'll have to be honest and say that I | _have_ seen it throw much more difficult to understand | nested errors referring to the underlying type | implementation when I was working on the type system | itself to create stricter type checks for functionality | that was previously unchecked (i.e. treated as "any" by | the compiler). | | The other thing to note is that these things are really | only doing type checking. If it becomes troublesome and | it does start to spit out type errors incorrectly, throw | unreadable errors, or otherwise become a maintenance | burden, these types are not particularly difficult to | remove, and by removing them you won't break your code. | Consider that to be the equivalent of removing a linting | rule or no longer requesting a review from a colleague. | Though it's probably a good idea to document _how_ to | remove these advanced checks for when people find them | annoying when someone leaves ;) | | Incorrect type checking implementation is probably the | biggest problem with these things getting complex, | though. If your type check is incorrectly throwing errors | for implementations that don't contain any errors at all, | that's going to set you back a lot! | jakelazaroff wrote: | Isn't this true for any abstraction, though? If the | person who wrote it is inaccessible, you have to | understand it by reading the source. | alpha_squared wrote: | Until it's the root cause of code not working as | expected, by another developer far removed from initial | implementation. Non-obvious code is harder to maintain. | Code is written for people, not machines; that means the | harder it is for people to maintain, the less useful it | actually is. | jorisd wrote: | Valid point. On the other hand, these kinds of advanced | types can prevent lots of bugs and maintenance work, and | may therefore be worth the day of debugging when it | breaks after two or three years of usage. | | I've used types like this in a pretty advanced TypeScript | UI project consuming lots of services to enforce compile | time errors. We were using generated TS clients for all | of the APIs we consumed, and the compiler would | automatically throw readable errors wherever we were | missing form fields or types became incompatible. I | committed the advanced type once, documented its usage, | and I don't think anyone has had to deal with it since, | whilst the types have steadily prevented errors. | | And even then: it's just type definitions. If it really | becomes a maintenance burden or someone has no clue what | it does, you can simply replace the type with "any" or | something similar and all of your problems are gone and | typescript won't complain anymore (at the expense of less | type error checking). | | EDIT: improved wording | dllthomas wrote: | Complicated types (like any complicated code) need their | own tests demonstrating that they do what the author | thinks (and fails to think about, as it's changed). | chana_masala wrote: | Isn't that the main challenge of being a programmer? | Anyways I don't find it hard to read, but I write TS like | that every day | bcrosby95 wrote: | Probably depends upon your job. The main challenge of | being a programmer, in the sorts of projects I work on, | is communication - with customers, management, and other | developers. | shadowgovt wrote: | TS has been around long enough that it suffers from the | 'obsolete tutorial' problem (one I first observed learning | C++): many of the utility types didn't exist when many of the | popular tutorials were first written. | goto11 wrote: | Depends on how much you can learn on one day I guess, but | TypeScript have lots of features which should be learned | before Utility types. For example type parameters, type | unions, the role of null and undefined, type assertions etc. | btown wrote: | I recently saw a code example in the VSCode repository where | Extract was used in a really awesome way. Say you have a | complex class with lots of fields _and_ methods, and you want | something that 's a specifier for either a constructor's | parameter or a query system. Often times, that will be very | similar to the underlying class, but just the fields in it - | excluding every member that's not a function. Instead of | rewriting every single field name for your new type, just have | your new type be Exclude<MyType, Function> or | Partial<Exclude<MyType, Function>> and the resulting type is | perfect for your needs. | | Pick is also really useful if you have an interface that passes | down a subset of complex things you get from a library; no need | to retype their types, just extend a Pick of the library type. | | In short, utility types are awesome! | cuddlecake wrote: | Funnily enough, I have done a session today where I live-coded | using the utility types in an effort to explain most of the | types listed on that page to a few of my colleagues. | Fedelaus wrote: | I think the unknown example is good but also somewhat confusing, | because implicit typing would understand what set of types could | be in that array at that moment. | | Is there another example someone could give for unknown which | isn't handled by implicit typing? | shadowgovt wrote: | interface ServerResponse { data: unknown; } | | ... this is the most common way I see unknown used. Then when | you fetch data from the server, you are reminded by the | compiler that you should do some duck-type checking on it to | make sure it's shaped correctly (since responses from a server | can be _any_ shape; is it a 200 with your data, or did a | caching layer vend you an old version of this data structure, | or is something catastrophically wrong and you 're seeing a 200 | where the payload is HTML saying "Set up your apache server," | etc.) | | BTW, TypeScript has another useful tool for tying the runtime | typing and static typing together: type guards. | function isUserRecord(x: unknown): x is UserRecord { | return (x as UserRecord).name !== undefined; } | | This is a boolean function but the type system understands that | in codepaths where it returns true, the 'x' argument is known | to have the UserRecord type. Great for codifying your type- | discernment logic. | conaclos wrote: | Note that `Readonly<T>` does not prevent a call to side-effect | methods when `T` is not among a predefined set of built-in types. | Indeed, it prevents such calls only on predefined types such as | arrays, maps, and sets. It could be more "accurate" to use | `readonly number[]` instead of `Readonly<Array<number>>` for | highlighting the difference. | cstrnt wrote: | That's right. But I wanted to to use `Readonly<T>` because it | can also be applied to plain objects while readonly cant | BoorishBears wrote: | That example disappointed me a little. | | It was an easy catch because I was told there's an issue, but | I'm surprised const arrays don't at least have a warning there. | Or even default to having readonly-like behavior | nosianu wrote: | Which "const" do you mean? The one in front of a variable | declaration _cannot_ make the array itself constant. That 's | because that "const" only refers to that variable itself, | which is just a pointer (except for the primitive types). | | The variable declaration "const" means this variable cannot | be changed to point to a different object. It says nothing | about the thing it points to and that is how that keyword was | designed in this language. It's Javascript (ECMAscript), not | Typescript. | | On the other hand, using Typescript (which only adds type | annotations but the actual code is ECMAscript apart from very | few small things such as "enums"), you can append "as const" | after an array though as type annotation, as in | const arr = [1,2,3] as const; // Type error: | "Property 'push' does not exist on type 'readonly [1, 2, 3]'" | arr.push(5); | | Which is the same as Readonly<type>. | | This "as const" annotation can be used for any object, not | just for arrays. Of course, it can only guard against known | methods of mutating an object, such as direct write access to | properties and known mutating function calls for known object | types such as the built-in ones (Array, Set, Map, etc., each | one needs the definitions for the readonly-version of its | type in the Typescript-bundled type library). | ketzo wrote: | Wow, the fact that `as const` has such a meaningful | difference from a variable declared as `const` seems... not | great. | | Surely they could have gone with, like, `final` or | `immutable` instead..? I'm sure they had their reasons. But | seems rough. | BoorishBears wrote: | This comment would be a lot shorter if you assumed I meant | Typescript in response to a Typescript article... | | But I digress, the point is in my experience Typescript is | very good about catching footguns left around by | ECMAScript. | | So I'm surprised there isn't some sort of catch for this | _as written in the article_ maybe behind a config flag, not | by rewriting the definition. ___________________________________________________________________ (page generated 2021-10-12 23:01 UTC)