[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)