Hacker Newsnew | past | comments | ask | show | jobs | submit | amake's commentslogin

> day to day transactions

Where is this happening?


Between me and my drug dealer

Lots of the rarer CJK ideographs are outside the BMP.

This was actually the first issue for my kanji learning app

https://github.com/runarberg/shodoku/issues/1

A classic utf-16 bug, where I failed to grab the two remaining bytes of these ideographs.


> it doesn't work for transitive deps unless those are specified by SHA as well, which is out of your control

So in other words the strategy in the docs doesn't actually address the issue


There's a repository setting you can enable to prevent actions from running unless they have their version pinned to a SHA digest. This setting applies transitively, so while you can't force your dependencies to use SHA pinning for their dependencies, you can block any workflow from running if it doesn't.


A lockfile would address this issue, with the added benefit that it would work


> There's no Noto Serif for CJK characters

Yes there is: https://fonts.google.com/noto/specimen/Noto+Serif+JP


> blowing it up out of proportion is just as toxic

One person decided that something wasn't for them. How is that in the same league as someone in a leadership position being unprofessional?


I personally don't care too much for hierarchies, so I didn't factor this in. You can be toxic at any level.


> You can be toxic at any level

And yet the context is extremely important.

> I didn't factor this in

That's how you get to false equivalencies.


>That's how you get to false equivalencies.

No, you're just putting something into it which doesn't matter to me. Not a false equivalency.


You said:

> blowing it up out of proportion is just as toxic

Making a false equivalency of the supposed toxicity of the commenter's post and the toxicity displayed in the article.

You can just take the L; you don't have to be performatively obtuse about it.


Might as well get rid of laws against murder because sometimes people commit murder anyway?


Not the same thing at all. There's consequences for murder, absolutely none for not abiding by this CoC; as clearly seen by the fact the posted remains as is.


A better analogy would be getting rid of laws against murder if its unevenly applied so people from a particular group always got away with it.


99% of my use of `satisfies` is to type-check exhaustivity in `switch` statements:

    type Foo = 'foo' | 'bar';
    
    const myFoo: Foo = 'foo';
    switch (myFoo) {
      case 'foo':
        // do stuff
        break;
      default:
        myFoo satisfies never; // Error here because 'bar' not handled
    }


I generally do this via a `throw UnsupportedValueError(value)`, where the exception constructor only accepts a `never`. That way I have both a compile time check as well as an error at runtime, if anything weird happens and there's an unexpected value.


The fact that there can be runtime type errors that were proven impossible at compile time is why I will never enjoy TypeScript.


TypeScript isn't primarily meant to be enjoyed.

It is meant to be a much better alternative to Javascript while dealing with the fact that the underlying engines use and existing programmers were used to Javascript.

That said I absolutely enjoy TypeScript, but that might be because I suffered from having to deal with Javascript from 2006 until TypeScript became available.


I have the exact same reason I enjoy typescript - raw dogging js before was an absolute nightmare of testing every single function for every possible value that might be thrown in and being able to handle shit data everywhere.

god-awful code.


As a C# dev, backend typescript is fantastic and the type system is light years ahead of C# in expressivity.

But the learning curve... no shit.


If Typescript is javascript with types bolted on, Rescript is javascript with types the way it should have been. Sound types with low complexity. https://rescript-lang.org/


That syntax is so alien to me as a JS/TS developer. I mean coffeescript was as well until JS slowly introduced it all.

It would be cruel for me to force ReScript onto the team because they'd all need to reskill. I could only use it for a private project and then hire exclusively for it afterwards


Really, that is surprising to hear. There are a couple of differences but most of the syntax looks the same to me, what part do you find alien?

The reskill problem is of similiar difficulty with learning a new framework I think. Especially because the language is rather simple compared to typescript (which is also its strength).

I do understand it is an uphill battle. The whole nobody get's fired for choosing IBM thing. The language is still unproven in the general perception. I do think that when it comes to libraries and frameworks I see a lot of developers choose new unproven stuff, more then they do languages.


Does this have a relation to Reason/Reason ML?



Agree wholeheartedly.

Writing TypeScript is better than JavaScript, but the lack of runtime protection is fairly problematic.

However, there are libraries such as https://zod.dev, and you can adopt patterns for your interfaces and there's already a large community that does this.


Zod is quite unpleasant to use, IME, an has some edge cases where you lose code comments.

From experience, we end up with a mix of both Zod and types and sometimes types that need to be converted to Zod. It's all quite verbose and janky.

I quite like the approach of Typia (uses build-time inline of JavaScript), but it's not compatible with all build chains and questions abound on its viability post Go refactor.


> we end up with a mix of both Zod and types and sometimes types that need to be converted to Zod

In my code, everything is a Zod schema and we infer interfaces or types from the schemas. Is there a place where this breaks down?


Not that I know of aside from code comments (which I like), but I much prefer writing TypeScript to Zod


Could you please elaborate on "patterns for your interfaces"?


Sure. You tend to think about the edges of your application.

1. Router

Tanstack Router: Supports runtime validation libraries such as z0d. So I have routes such as example.com/viewer/$uuid/$number, it should 400 if those aren't actually validate uuid and numbers.

React Router: Supports Types, but every type is a string because, well, they technically are, but this isn't useful in practice in my opinion. There are 3rd party libs such as: https://github.com/fenok/react-router-typesafe-routes

2. API

Lets say you're making your API public to clients you can't trust to send the correct data ( which probably also includes your own client ).

https://www.npmjs.com/package/express-openapi-validator

This library advertises validating both your input and your output

3. State

https://github.com/pmndrs/zustand/discussions/1722

4. Database

https://www.npmjs.com/package/prisma-zod-generator

5. Forms

https://medium.com/@toukir.ahamed.pigeon/react-hook-form-wit...

6. ENV

https://jfranciscosousa.com/blog/validating-environment-vari...

Obviously checks on the agent are primarily a DX/UX thing, whilst checks on the server step are also security controls.


Isn't that not necessarily out of the ordinary though? What if there's a cosmic ray that change's the value to something not expected by the exhaustive switch? Or more likely, what if an update to a dynamic library adds another value to that enum (or whatever)? What some languages do is add an implicit default case. It's what Java does, at least: https://openjdk.org/jeps/361


> What if there's a cosmic ray that change's the value to something not expected by the exhaustive switch?

I could forgive that.

The TypeScript case is more like "what if instead of checking the types we just actually don't check the types?".


This is how all static type checking works. What programming language do you have in mind that does static type checking and then also does the same type checking at runtime? And what would you expect this programming language to do at runtime if it finds an unexpected type?


I think the point is that other languages make guarantees that ensure you don't have to do any runtime checking. In TypeScript, it's far too easy (and sometimes inevitable) to override the type checker, so some poor function further down into the codebase might get a string when it expects an object, even though there are no type errors.


Compiler exhaustion is such a useful feature. I can’t believe TS doesn’t have it.


> The fact that there can be runtime type errors that were proven impossible at compile time is why I will never enjoy TypeScript.

The "impossibility" is just a trait of the type definitions and assertions that developers specify. You don't need to use TypeScript to understand that impossibilities written in by developers can and often are very possible.


My first introduction to TypeScript was trying to use it to solve Advent of Code.

I wrote some code that iterated over lines in a file or something and passed them to a function that took an argument with a numeric type.

I thought this would be a great test to show the benefits of TypeScript over plain JavaScript: either it would fail to compile, or the strings would become numbers.

What actually happened was it compiled perfectly fine, but the "numeric" input to my function contained a string!

I found that to be a gross violation of trust and have never recovered from it.

EDIT: See https://news.ycombinator.com/item?id=46021640 for examples.


> I thought this would be a great test to show the benefits of TypeScript over plain JavaScript: either it would fail to compile, or the strings would become numbers.

You're just stressing that you had a fundamental misunderstanding of the language you were using when you first started to use it. This is not a problem with the language. You simply did not understood what you were doing.

For starters, TypeScript does not change the code you write, it only helps you attach type information to the code you write. The type information you add is there to help the IDE flag potential issues with your code. That's it.

See how node introduced support for running TypeScript code: it strips out type info, and runs the resulting JavaScript code. That's it.

If your code was failing to tell you that you were passing strings where you expected numbers, that was a bug in your code. It's a logic error, and a type definition error.

Static code analysis doesn't change the way you write code. That's your responsibility. Static code analysis adds visibility to the problems you're creating for yourself.


No tool is perfect. What matters is if a tool is useful. I've found TypeScript to be incredibly useful. Is it possible to construct code that leads to runtime type errors? Yes. Does it go a long way towards reducing runtime type errors? Also yes.


> No tool is perfect. What matters is if a tool is useful

Some tools are more perfect and more useful than others.

Typescript's type system is very powerful, but without strict compile-time enforcement you still spend a lot of effort on validating runtime weirdness (that the compiler ought to be able to enforce).


Yes that's true, but there's effort to consider on both sides of design decisions like those TypeScript has made. Much of the compile time behaviour comes from the decision for TypeScript to be incremental on top of JavaScript. That allows you to start getting the benefit of TS without the effort of having to rewrite your entire codebase, for example. Having used TS for many years now I feel that the balance it strikes is incredibly productive. Maybe for other folks/projects the tradeoff is different - but for me I would hate going back to plain JS, and there's no alternative available with such tight integration with the rest of the web ecosystem.


Have you seen ReScript? Of course it is not as popular as typescript but it improves on all the bad parts of typescript. You'll get sound types with the pain points of javascript stripped out. Because it compiles to readable javascript you are still in the npm ecosystem.

You don't have to rewrite your whole codebase to start using it. It grows horizontally (you add typed files along the way) compared to typescript which grows vertically (you enable it with Any types).

The point is that we don't have to move back to plain js. We have learned a lot since typescript was created and I think the time has come to slowly move to a better language (and ReScript feels the most like Javascript in that regard).


> Typescript's type system is very powerful, but without strict compile-time enforcement you still spend a lot of effort on validating runtime weirdness (that the compiler ought to be able to enforce).

That's something that you own and control, though. Just because TypeScript allows developers to gently onboard static type checking by disabling or watering down checks, that does not mean TypeScipt is the reason you spend time validating your own bugs.


> Just because TypeScript allows developers... does not mean TypeScipt is the reason you spend time validating your own bugs

Unfortunately, taking an ecosystem-wide view, it means exactly that. If one of my dependencies hasn't provided type stubs, or has provided stubs, but then violated their own type signatures in some way, I'm on the hook for the outputs not matching the type annotations.

In a strict language, The compiler would assert that the dependency's declared types matched their code, and I'd only be on the hook for type violations in my own code.


It's way better than having to write untyped JavaScript though


TypeScript is neither sound nor complete and was defined that way, beating out other competitors that were sound and/or complete.

What I mean to say is - TypeScript isn't proof.


That scenario is usually either misuse of escape hatches (especially at API boundaries) or a misunderstanding of what Typescript actually guarantees.


Not really, I provided these examples a couple weeks ago on another HN thread. TypeScript is simply unsound.

https://www.typescriptlang.org/play/?#code/MYewdgzgLgBAllApg...

https://www.typescriptlang.org/play/?#code/DYUwLgBAHgXBB2BXA...


Aren't these bugs that could be "simply" reported and fixed? Or maybe those would get a label "not a bug" attached by the TS creators for some reason?


Both are by design. Array covariance is a common design mistake in OOP languages, which the designer of TypeScript had already done for C# but there they at least check it at runtime. And the latter was declared not-a-bug already IIRC.

TypeScript designers insist they're ok with it being unsound even on the strictest settings. Which I'd be ok with if the remaining type errors were detected at runtime, but they also insist they don't want the type system to add any runtime semantics.


"By design", for me, doesn't say that it can't be changed — maybe the design was wrong, after all. Would it be a major hurdle or create some problems if fixed today?


Perfect examples of the kind of thing I'm talking about, thank you.


In the first example you deliberately create an ambiguous type, when you already know that it's not. You told the compiler you know more than it does. The second is a delegate, that will be triggered at any point during runtime. How can the compiler know what x will be?


First example: you're confusing the annotation for a cast, but it isn't; it won't work the other way around. What you're seeing there is array covariance, an unsound (i.e. broken) subtyping rule for mutable arrays. C# has it too but they've got the decency to check it at runtime.

Second example: that's the point. If the compiler can't prove that x will be initalised before the call it should reject the code until you make it x: number|undefined, to force the closure to handle the undefined case.


For the first one, the compiler should not allow the mutable list to be assigned to a more broadly typed mutable list. This is a compile error in kotlin, for example

    val items: MutableList<Int> = mutableListOf(3)
    val brokenItems: MutableList<Any> = items


> The second is a delegate, that will be triggered at any point during runtime. How can the compiler know what x will be?

x is clearly defined to be a number. The compiler should produce an error if the delegate captures x before it has a value assigned.


If it only works when you write the types correctly with no mistakes, what's the point? I thought the point of all this strong typing stuff was to detect mistakes.


Because adding types adds constraints across the codebase that detect a broader set of mistakes. It's like saying what's the point of putting seatbelts into a car if they only work when you're wearing them - yes you can use them wrong (perhaps even unknowingly), but the overall benefit is much greater. On balance I find that TypeScript gives me huge benefit.


Same here, you can also use the same function in switch cases in Angular templates for the same purpose. Had no idea you could achieve similar with `satisfies`, cool trick.


That's great, I'm going to use that one in the future.


That's very clever!


We have this nifty util in our codebase:

```ts

/*

* A function that asserts that a value is never.

* Useful for exhaustiveness checks in switch statements.

*/

export function assertNever(x: never): never {

  // eslint-disable-next-line @typescript-eslint/restrict-template-expressions

  throw new Error(`Unexpected object: ${x}`)
}

```


https://typescript-eslint.io/rules/switch-exhaustiveness-che... if that is something you're not aware of!


I would highly recommend the ts-pattern [1] library if you find yourself wanting exhaustive switch statements! The syntax is a bit noiser than case statements in simple cases, but I find it less awkward for exhaustive pattern matching and much harder to shoot yourself in the foot with. Once you get familiar with it, it can trim down a /lot/ of more complicated logic too.

It also makes match expressions an expression rather than a statement, so it can replace awkward terenaries. And it has no transitive dependencies!

[1]: https://github.com/gvergnaud/ts-pattern


Nice. I didn’t know I can now replace my “assertExhaustive” function.

Previously you could define a function that accepted never and throws. It tells the compiler that you expect the code path to be exhaustive and fixes any return value expected errors. If the type is changed so that it’s no longer exhaustive it will fail to compile and (still better than satisfies) if an invalid value is passed at runtime it will throw.


I thought the same thing. I also have an assert function I pull in everywhere, and this trick seemed like it would be cleaner (especially for one-off scripts to reduce deps).

But unfortunately, using a default clause creates a branching condition that then treats the entire switch block as non-exhaustive, even though it is technically exhaustive over the switch target. It still requires something like throwing an exception, which at that point you might as well do 'const x: never = myFoo'.


I still keep my assertNever function because it will handle non-exhaustiveness at runtime.


This is what I do:

   class AbsurdError extends Error {
     constructor(public value: unknown, message: string) {
       super(message);
       this.name = 'AbsurdError';
     }
   }
   
   function absurd(value: never, message: string) {
     throw new AbsurdError(value, message);
   }
Including an error message and an error type helps if one does slip through to runtime. Additionally, the AbsurdError can be caught and escalated appropriately. And finally the absurd function can be used in an inline ternary etc. where alternatives like throw cannot.


TIL.


“Pro” users are using containers, venvs, version managers (nvm, rvm, etc.). They definitely aren’t installing project-specific stuff directly to the system.


Your analogy is infinitely more baffling to me than OP's comment.


Really? Almost always I feel the diff captures only the how, not the why.


Guidelines | FAQ | Lists | API | Security | Legal | Apply to YC | Contact

Search: