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