maxfurman 3 days ago

The difference between `any` and `unknown` is that `any` is an escape hatch from the type system. `any` can be used anywhere and will satisfy any type constraint. `any` is how the developer says to the type system "trust me, I know what I'm doing, don't worry about this particular value." Unknown, on the other hand, is for untyped code from an imported JS library, JSON data received from the network that may or may not match the API spec, etc. Code that does have a type, but that type needs to be confirmed at runtime and can be narrowed through type guards.

  • theonething 3 days ago

    > I know what I'm doing

    `any` can also be an indicator of the exact opposite.

  • piyh 3 days ago

    I went through my code base and found one use of `any` that was worth keeping around, the rest could be changed to `unknown` and better code resulted from it.

  • thoughtspile 3 days ago

    I think "unknown" is more than that! Basically it's a way to say you don't care about the type, as in Record<string, unknown> or P extends Promise<unknown>

    • sirmarksalot 3 days ago

      This is technically correct, but I suspect it's pretty rare that you would choose "unknown" over a generic parameter. Even if you're writing an algorithm that doesn't care about the data type, it's still probably going to be used by application code that does care, and you'll want to support that need by passing the type through, otherwise the application will be forced to downcast.

      • thoughtspile 3 days ago

        I often use it to infer one generic parameter:

        type Output<Fn> = Fn extends ((a: unknown) => infer Out) ? Out : never;

    • jax_the_dog 3 days ago

      How is unknown better than any in this case? If you do not care about the data type, wouldn’t you say that “any” data type is acceptable? If you say “unknown” that means that there is possibly some data type that would break your function.

      • madeofpalk 3 days ago

        The problem with using any where you don't care about the type is that someone can come along and make unchecked assumptions about what type it actually is. Unknown is spicier as it'll prevent developers from assuming it's a string or object or something.

        • jax_the_dog 3 days ago

          That makes some sense to me.

          I’m not a TypeScript dev so I apologize for the stupid question. Is there any functional difference between the two? I.e. is there a case where using Unknown instead of Any will result in some sort of “compile” time error opposed to a runtime?

          • WorldMaker 3 days ago

            Yes. `any` really does turn off all type checking. You see that offhand mentioned in this article talking about some of the paradoxes of `any`. `unknown` is still type checked and is arguably "merciless" type-checked that to do much of anything with an `unknown` you have to check for a more specific type first or the compiler returns an error that what you are doing isn't known to be valid for `unknown`.

            Some of that happens "automatically" at this point in the large number of ways that types can now be narrowed implicitly in Typescript (type guards [library functions], type asserts, the `typeof` runtime operator, the `in` runtime operator [as of recently], etc), so it can feel like `unknown`/`any` are the same up to a point, that point being where the runtime type is trivially known based on if statements and library functions around your use of the type.

            (Fun fact: `any` predates most type narrowing and `unknown` by several major Typescript versions. So `any` beyond just being the final "escape hatch" from type checking is also something of a legacy tool.)

          • thoughtspile 3 days ago

            Almost every case where you use the value results in an error with unknown, but not with any:

              let danger: any;
              // These all compile:
              danger(), 9 / danger, danger.access, danger.map(x => x \* 2)
            
              let safe: unknown;
              // These all explode in TS:
              safe(), 9 / safe, safe.access, safe.map(x => x \* 2)
      • thoughtspile 3 days ago

        Saying a value is "unknown" means making no assumptions about the value. It might be a null, a number, a function, you don't care because you aren't going to do anything with this value. If you call() it, or read.some.property, TS complains because you're making assumptions about the object that TS did not ensure.

    • edgyquant 3 days ago

      But you have to care about the type with unknown? For instance calling this on an unknown

          obj.value
      
      Will return a type error that value doesn’t exist on unknown. While calling it on an ant is valid typing
      • _greim_ 3 days ago

        > But you have to care about the type with unknown?

        It's better to say `unknown` is a promise that you won't care about the type. When you write `obj.value`, you're caring about the type. TypeScript sees that and says "hey, you promised you wouldn't care about this type."

        > While calling it on an ant is valid typing

        It's still invalid, same as if you'd used `unknown`. It just suppresses the type error. The presence of `any` in a codebase is a very real risk. The only reason it exists is to allow incremental porting from JavaScript to TypeScript.

      • thoughtspile 3 days ago

        Here, you expect obj to have a property called "value", so I'd say you do care about the type of obj. A valid type would be obj: { value: blah }

  • joshfee 3 days ago

    `any` should really be called `every` to match the semantics, it says "this value is an instance of every type, and therefore you can do anything with it".

    And you might read that and thing "but that's crazy, its not possible for a value to be an instance of everything!" and you'd be right - the `any` type in TS is in fact crazy and you should not use it.

    • consilient 3 days ago

      The intersection of all typescript types is `never`. `any` is approximately equivalent to `unknown` in contravariant positions and `never` in covariant ones.

  • dclowd9901 a day ago

    That’s a convenient way of thinking about it. A more logically correct way of thinking about it is:

    “any” can be any possible type

    “unknown” is a specific stand-in type. It does not represent all types but is a subset of all types, which allows you to narrow its definition from nothing.

    Unknown in practice is closer to a generic in terms of usage and semantics than “any”. It’s like a generic generic except it’s not useful until you direct it. It basically lets you kick the can down the road in your code of having to make a type decision, which is very useful for writing composable and uncoupled code.

jakelazaroff 3 days ago

I might be missing something, but isn't this line backwards?

> Subtype of type A is a subset of type A. Supertype is a superset. Easy.

Subtype of type A is actually a superset of type A, since it contains at least all the properties of A.

If you had (contrived example) a Dog class that inherited from an Animal class, Dog would be a subtype of Animal, but its additional properties (say, a bark() method) mean that it actually has a superset of the properties in Animal. And vice versa: Animal is a supertype of Dog, but it's a subset because it contains only the properties that Dog inherits.

  • RyanCavanaugh 3 days ago

    Nope. This is a common confusion.

    The set of properties of objects and the sets of objects themselves have a complementary relationship when it comes to union / intersection and subset / superset.

    Let's define a "property" as being a predicate that is true for all elements of a set. For example, a collection of red objects has the "red" property.

    A subset of a set of objects can only have the same or more properties than the set you started with. If you take a bunch of marbles of varying colors, selecting a subset of those marbles can yield a set with a new property (such as them all being red), but they are guaranteed to have the "marble" property.

    A superset of objects can only have the same or fewer properties than the set you started with. Adding marbles to an existing set of marbles can only make "they are all red" become false (if it was true to begin with).

    Union and intersection have the same duality; the union of two sets has the intersection of its properties, and the intersection of two sets has the union of its properties.

    These results come from logic, not TypeScript.

    • LudwigNagasena 2 days ago

      I think that interfaces in TypeScript unfortunately contribute to that confusion. I can do

        interface Point2D {
          x: number
          y: number
        }
      And then I can do

        interface Point3D extends Point2D {
          z: number
        }
      And that "extends" really throws off many people because in reality the type of Point2D is implicitly

        {
          x: number
          y: number
          [anything_else in string]: unknown
        }
      
      So Point3D actually narrows the type of Point2D even though the notation suggests that it widens it. Fortunately, the type notation is less misleading because I can do

        type Point3D = Point2D & {
          z: number
        }
      Without any misleading "extensions".

      It seems like it all stems from the tight coupling between types and classes in a typical OOP paradigm. When you create a class Animal you create a type Animal that should conform to specific rules, but when you instantiate that class you create an object that conforms to a narrower set of rules. E.g. an object instantiated from class Animal won't be able to woof() even though the type "Animal" easily allows it. This leads to the mental model of "extending" base class with new fields and methods. And even though it indeed extends the class Animal, it narrows the type Animal.

      • goto11 2 days ago

        "Extends" does not mean it extends the type to contain more members, it means it extends instances to have more properties, thereby creating a subset or subtype.

        Perhaps it is confusing that TypeScript uses terms from both set-theory (union, intersection) and OO (extends, implements). But there is no contradiction.

      • dragonwriter 2 days ago

        > So Point3D actually narrows the type of Point2D even though the notation suggests that it widens it.

        The notation suggests that it extends the definition with additional restrictions. Which it does. Which, yes, produces a narrower scope. That’s how definitions work.

        • LudwigNagasena 2 days ago

          In mathematical logic, for example, saying that A is an elementary substructure of B is equivalent to saying that B is an elementary extension of A. The same with subfields and extension fields. Those concepts are opposite.

          Usually in OOP you extend a class with additional implementation. Intersection of types means union of their members and vice versa. Getting into a situation where extending X gives you a sub-X seems like an unfortunate case of mixing two mental models that behave in a dual manner.

    • Tainnor 2 days ago

      This depends on what you mean by "property".

      Universal properties are preserved "downwards" i.e. in subsets (more properly, substructures). Existential properties are preserved upwards, i.e. in supersets (extensions).

      For example, the property "there exists an element that equals itself when added to itself" is preserved in a superset.

      If you have sentences that mix quantifiers (e.g. "for all epsilon, there is a delta ..." or even just "every element has an inverse"), then all bets are off.

      The branch of mathematics where this is studied (and rigorously proven) is model theory.

      edit: I guess by "property" (given the context of the discussion) you might just mean "function" or "method", in which case, yes, a function defined on a set is also defined on a subset but not necessarily vice versa, so there are in a certain sense "more" functions defined on the subset.

    • yitr 3 days ago

      maybe a dumb question, but why does wikipedia say typescript is a superset of javascript?

      https://en.wikipedia.org/wiki/TypeScript

      • sirmarksalot 3 days ago

        Because Typescript is designed to accept any valid Javascript program, the set of valid statements in a Typescript program is a superset of the set of valid statements in a Javascript program. Typescript contains all the rules of Javascript, and then adds some more, but never in a way that contradicts the requirement that a plain Javascript program should compile, so that also means the language specification itself is a strict superset of Javascript.

        • lhorie 3 days ago

          > Typescript is designed to accept any valid Javascript program

          That's not strictly true though. `a<b,c>(d)` is valid Javascript but Typescript treats it as a different syntactic construct[0].

          [0] https://www.typescriptlang.org/play?#code/C4TwDgpgBARlC8UB2B...

          • oblosys 3 days ago

            With these declarations your fragment is valid in both JavaScript and TypeScript:

                class b {}
                class c {}
                const d = JSON.parse
                const a = Promise.prototype.then.bind(Promise.resolve(1))
            
                console.log(a<b,c>(d))
                // TS output: Promise { <state>: "pending" }
                // JS output: false false
            
            TS playground: https://tsplay.dev/mAdeZN
          • qayxc 3 days ago

            It is strictly true. a<b,c>(d) is only valid JavaScript iff a, b, c, and d are values. The TypeScript expression a<b,c>(d) where b and c are types is NOT JavaScript, since JavaScript doesn't have type expressions. The statement that every JavaScript program is a valid TypeScript program does not imply the opposite - hence strict superset.

            edit: there is indeed an edge case with the parentheses that throws the TypeScript parser off even if only values are involved.

            • lhorie 3 days ago

              See the sibling comment from oblosys for an example where the exact same code can give different results in vanilla JS vs after a tsc pass. As you can see there, the problem is that an identifier can simultaneously represent a value and a type.

              This is different than JSX or hashbang, where the set of non-JS syntax cannot legally overlap with existing syntax/semantics.

          • sirmarksalot 2 days ago

            I guess this is a distinction between intent and results. Typescript is intended to be a strict superset of Javascript. In practice, there are edge cases like these.

        • hbrn 3 days ago

          Here are 3 statements that were made in this thread:

          - typescript is a superset of javascript

          - superset of objects can only have the same or fewer properties

          - Typescript contains all the rules of Javascript, and then adds some more

          Do you see where the confusion is coming from?

          > that also means the language specification itself is a strict superset of Javascript

          This is where I disagree.

          TS as a language has more properties than JS, but less rules. I.e. you can create Javascript language out of Typescript by adding more rules (constraints).

          But TS as a spec has more rules than JS spec.

          TS as a language is a superset of JS language. TS as a spec is a rough subset of JS spec.

          • eevilspock 3 days ago

            You're making this harder than it actually is by conflating a bunch of things, comparing apples and oranges. Sets defined by type vs sets defined by lists of properties, specs, rules etc.

            For types, just draw the Venn diagrams:

            - The set of objects of Type A is entirely within the set of objects of Subtype of A. Subtype of A is the superset.

            - the set of things that are Dogs (class or real world) is entirely within the set of things that are Animals.

            - the set of things that are Javascript programs is entirely within the set of things that are Typescript programs.

            • hbrn 3 days ago

              > Sets defined by type vs sets defined by lists of properties, specs, rules etc.

              Recursive explanations are useless: "types are just sets defined by types". That's why we are trying to define them through other means.

              Also TS has structural type system, which is literally about comparing properties.

              • Tainnor 2 days ago

                That's not what GP wrote - they wrote that there is a difference between "the set of all instances of type A" and "the set of all properties satisfied by all instances of type A". There's nothing recursive about that. And this is all completely independent of the question of whether a language is structurally or nominally typed.

              • strbean 3 days ago

                (butting in)

                What is recursive about the parent comment?

                • hbrn 3 days ago

                  It's recursive in the context of "making sense of TS using set theory". If you understand sets, but not types, statements about "sets defined by types" are meaningless.

          • bazoom42 3 days ago

            > superset of objects can only have the same or fewer properties

            This is potentially misleading. A superset will include more objects and therefore may include more individual properties. But type checking is about what can be safely assumed about all members of a set, so more different objects in the set will constrain the type more.

            Object is a superset of Date because the set of objects includes alle Dates but also things which are not dates and have different properties. But in the context of type checking, object is more constrained because there are fewer properties which all members of the set are guaranteed to have.

            Typescript is a superset of Javascript because all Javascript programs are also Typescript programs. There is no contradiction.

            • hbrn 3 days ago

              > object is more constrained because there are fewer properties which all members of the set are guaranteed to have.

              In my mind it's the opposite: object is less constrained, because there are fewer requirements you need to fulfill to be considered an object. Object type is very permissive.

              The more constrained type is, the fewer objects it's set will contain. The more constrained the type is, the more rules it enforces. Seems intuitive, isn't it?

          • goto11 2 days ago

            TypeScript is a superset of JavaScript because any JavaScript program is also a Typescript program but not vice versa. There is no contradiction.

            > TS as a language is a superset of JS language. TS as a spec is a rough subset of JS spec.

            This does not make any sense. A spec which fully describes TypeScript including runtime behavior would include the JavaScript spec and therefore be a superset. The TypeScript spec itself is in no way a subset of the JS spec, sine it descries features and syntax which does not exist in vanilla JavaScript.

            • hbrn 2 days ago

              This is how you should think about it: Spec > Language > Code.

              Code is an instance of Language (and in structural typing land it can be compatible with multiple languages). Language is an instance (implementation) of Spec.

              > sine it descries features and syntax which does not exist in vanilla JavaScript

              That's exactly why TS spec is a subset. Take example from the article:

                type B = true extends boolean ? 1 : 0; // 1
              
              Type B is a subset of boolean, meaning there are less objects in the world that satisfy type B.

              There are less language implementations in the world that satisfy TS spec than JS spec. Every language that satisfies TS spec also satisfies JS spec. TS spec adds more constraints (requirements) to language implementation compared to JS spec, it is more strict, therefore it's a subset.

              • goto11 a day ago

                So you are saying the implementation of the Typescript language are a subset of all JavaScript implementations? I guess that is true in a certain sense. But this does not contradict the fact that TypeScript as a language is a superset of JavaScript - rather it follows logically.

                (Although in reality the Typescript implementation is a preprocessor, intended to use together with a regular JavaScript engine.)

                • hbrn 20 hours ago

                  Kind of. The language gets confusing because we use subset and superset next to each other, but we put different meaning on them.

                  {foo: number, bar: string} as type is a subset of both {foo: number} and {bar: string}. It requires it's members to have both properties. You can construct this type using intersection:

                    type Foo = {foo: number}
                    type Bar = {bar: string}
                    type FooBar = foo & bar
                  
                  
                  {foo: 1, bar: 'hello'} as object is a superset of {foo: 1} and {bar: 'hello'}. It contains both properties. We can construct this object using union (I use pipe instead of spread to illustrate the idea):

                    let foo = {foo: 1}
                    let bar = {bar: 'hello'}
                    let foobar = foo | bar
                  
                  
                  When I'm saying TS is a superset of JS, I mean it in the object sense. It has all the properties of JS, and some more. All JS programs are also TS programs, but not vice versa. There's more JS programs in the world than TS programs.

                  When I'm saying TS spec is a subset of JS spec, I mean it in a type sense. Language is an instance of a spec. TS spec has all of the requirements of JS spec, and some more. All TS implementations (languages) will contain JS implementations, but not vice versa. There's more JS implementations in the world, than TS implementations (assuming you can build TS as compiler/interpreter instead of transpiler).

                  • goto11 7 hours ago

                    > All JS programs are also TS programs, but not vice versa. There's more JS programs in the world than TS programs.

                    I think you are confusing terminology, because those two statements are contradictory.

          • jbirer 3 days ago

            You are right, the statements are contradictory. It's strange to see how nobody questions marketing buzzwords from Microsoft.

      • thoughtspile 3 days ago

        Because all valid JS code is also valid TS code (considering implicit any). Or, put another way, the set of all valid JS programs is a subset of all valid TS programs.

    • antihipocrat 3 days ago

      In your example with marbles, the superset has the property of every element being comprised of a set of colors, let's say {black, red, blue}.

      The subset is {red}.

      Can't every possible property we can conceive of a subset be constructed in a similar fashion such that the superset contains at least the same number of elements and never less?

      • lmm 2 days ago

        > Can't every possible property we can conceive of a subset be constructed in a similar fashion such that the superset contains at least the same number of elements and never less?

        What do you mean? A superset always contains at least the same number of elements as a subset, by definition. A superset generally has fewer or at least weaker properties; "colour is one of A, B or C" is a weaker property than "colour is A", and there's no real difference between that and saying the superset doesn't have the property at all (I could say "my subset consists of shiny marbles; my subset has the property that they're shiny and my superset does not have that property", or I could say "my subset has the property that shininess is true, and my superset has the property that shininess is true or false", and there isn't really any difference once you get down to the set-theoretic level - a property is just a boolean-valued function). The only superset that has exactly the same properties as the set itself is the set itself (since "is a member of the subset" is a valid property).

        • antihipocrat 2 days ago

          Ok, how about I define a new property that my superset contains a 'rainbow' of colors. The subset has only one of the colors, it doesn't contain the rainbow property. The original post claimed that the superset always has the same or fewer properties. I was questioning whether this was true.

          You've mentioned that it is generally true, which seems like a better way to describe the concept.

          -- edit: I understand now, in my example above the subset must contain a property by which it could be identified. The original rainbow property is lost but a new one is gained.

          • lmm 2 days ago

            > Ok, how about I define a new property that my superset contains a 'rainbow' of colors. The subset has only one of the colors, it doesn't contain the rainbow property.

            You're mixing levels. The property is something satisfied by members of the set, not by the set as a whole.

      • Karrot_Kream 2 days ago

        The way "subset of properties" is being used here is loose and that's where the confusion is arising from. If we want to be a bit more rigorous:

        * Define O as a set of possible (finite) objects

        * Define properties as functions P: O -> {0, 1}

        * A given property then is a function of the form p(o) = 1 for all o in O that satisfy the property p.

        You can use property functions to generate subsets of O ({ o for o in O where p(o) = 1 }). The intersection of these generated subsets of multiple property functions will be strict subsets (strictly "smaller" as O is finite) of O as long as at least one property function is not of the form P(o) = 1 for * in O. Generally assuming that each property function generates strict subsets, applying more properties derives a smaller intersection set. That's all.

        • antihipocrat 2 days ago

          Thanks for that, it makes sense using your definition. I'm curious as to whether you agree with the parent comment that the subset could contain 'more' properties than the superset.

          My understanding from your stricter definition is that (by definition) all potential properties (o for o in O where p(o) = 1) must be contained within O.

          If the answer is that the superset O has fewer properties where every p(o) = 1 then I agree, and I was overthinking it

          • Karrot_Kream 2 days ago

            > If the answer is that the superset O has fewer properties where every p(o) = 1 then I agree, and I was overthinking it

            It's just this honestly. A superset will generally have fewer properties which apply across the whole set.

  • thoughtspile 3 days ago

    You're making the same mistake as I sometimes do, thinking in terms of "object shape" or "functionality". Here, I use "subset" in a sense of "all values that belong to Sub also belong to Super", in line with a set-theoretic view of types in the post. Also see subsets on wiki: https://en.wikipedia.org/wiki/Subset

    Try this one: A is subset of B iff all the values that belong to A also belong to B. In your example (and in real life), every dog is an animal, so dogs are a subset of animals.

  • hbrn 3 days ago

    Types are not features, they are constraints.

    "at least all the properties of A" means "all of the constraints of A and some more". Subtype of A is more narrow than A, even if it means that object of subtype A has to have more fields.

  • epolanski 3 days ago

    > Subtype of type A is a subset of type A. Supertype is a superset. Easy.

    I think it is correct.

    Example:

    type A = { a: number }

    type B = { a: number, b: number }

    B is not a superset of A, it's a subset and subtype of A. You can use B every time you want to use A.

    In other words, of all types that can be assigned to A or used in functions and types expecting an A, B is one of those subsets.

    type C = { a: number, c: number }

    is another subtype of A.

    A "subtype" C can be substituted in place of its "supertype" A.

    Both C and B are subsets of A, in other words: in the infinite set of values of type A, some can be grouped in a type B or C. C itself is a subtype of A.

  • feoren 3 days ago

    In your case, dogs are only those animals that bark. That's a subset of animal. Think of it this way: can you think of an object that satisfies (is-a) Dog, but not Animal? No. Can you think of an object that satisfies Animal, but not Dog? Yes. So Dog ⊂ Animal.

    hbrn has it right calling those properties constraints, not features. With anything but a sealed class, the properties that are not present are unspecified rather than missing.

    And indeed sealed classes feel very "un-set-theoretic" to me.

  • jotaen 3 days ago

    I think what’s confusing here is that there are two mental models that use the word “type” in an opposing way: if you think of types in terms of set theory, then “subtype of type A is a subset of type A”. But if you think of types in terms of a class hierarchy (like in Java), then by saying “subtype of type A is a superset of type A” you actually mean “an inherited type (class) that is further down in the class hierarchy has the same or more properties than the parent type (class) that is further up”.

    So I actually would agree that your original statement is valid, assuming that you refer to “types” as “classes” in the sense of Java class hierarchies.

  • Koshkin 3 days ago

    > 4. A extends B ... can be read as "A is subset of B".

    I agree, it is unnatural to think of a subset as "extending" the set it is part of.

    • Jtsummers 2 days ago

      Yeah, it's a common way of telling people how to read "A extends B", but it's not quite right. The two relationships are related (no pun intended) but they aren't the same relationship. `A extends B` means (in the OO world) that A adds fields, methods, or changes the behavior of methods to B. `A is a subset of B` means that all As are Bs, but some Bs may not be As.

      It happens that `A extends B` introduces a subset relation between them, but it's not correct to describe it as the same as the subset relation.

scotty79 3 days ago

> Why does 0 | 1 extends 0 ? true : false evaluate to false?

Because 'extends' really means 'is assignable to'.

I feel like most of the questions from the post might be answered fairly easily by using reasoning of 'is assignable to'.

Things assignable to A&B are things assignable to both A and B.

Things assignable to A|B are things assignable to A or to B.

'never' means a type that nothing is assignable to and is assignable to everything (bit weird because it usually doesn't have any values but you can create value of type never with the use of 'as').

'unknown' means a type that everything can be assigned to but it's not assignable to anything (except itself and any).

'any' means a type that everything is assignable to and that is assignable to everything (except never).

Ok. Ok. {} types is bit weird way to denote interfaces. :-)

  • thoughtspile 3 days ago

    Fair enough, "is assignable to" is another synonym for "is subset of". I find it much easier to reason about things I can visualize, like sets, which is why I love my set interpretation.

    Edit: besides, it's quite unintuitve that "never" is assignable to anything. How can never be something?

    • lmm 2 days ago

      > Fair enough, "is assignable to" is another synonym for "is subset of".

      Only if you're happy to conflate members and sets, which is pretty confusing IMO. If I write a = b I don't generally think of b as being a set, even though in some sense it is; "b is an expression with this type" is a much more natural way of thinking to me.

    • scotty79 3 days ago

      > it's quite unintuitve that "never" is assignable to anything

      Yeah, at first I was mislead by this.

      Initially I thought never is opposite of any while, it turns out, it's opposite of unknown. And any is just super weird, dynamic wildcard thing that is as large or as small as needed for any given operation (but not as small as never).

    • scotty79 3 days ago

      It's harder for me to make that connection but thank you for your write up. It's really great.

    • Danoha 3 days ago

      Because "never" is an empty set and empty set is a subset of all sets.

      • thoughtspile 3 days ago

        Exactly, that's why I wrote the article =) Makes perfect sense in "set world", but not in "common sense" based on your feeling of the word "never"

  • LudwigNagasena 2 days ago

    Yeah, `extends` seems like a misnomer that confuses people.

clord 3 days ago

> unknown is the set of all JS values. any is a paradoxical set that includes everything, but might also be empty.

Not really a paradox.

- `unknown` is the intersection (&) of all types, including an internal one that has no shape or characteristics. `unknown | {int: number}` should be `{int: number}`. `unknown & T` is always `unknown`, any characteristic T has will be discarded by the intersection.

- `any` is the union (|) of all types, including that internal one that can be anything, and yes, `unknown` too. `(any & {int: number})` should be `{int: number}`. also, `const x: any = 5 as unknown`. Union with `any` should always produce `any`.

  • bazoom42 3 days ago

    I think you have switched union and intersection. ’Unknown’ is a type which can be anything, so it is the union of all types. ‘Never’ is the empty set of types.

    ‘Any’ does not really fit into a set theoretical model because it can be assigned to anything and anything can be assigned to it, so it is both a supertype of anything and a subtype of anything. Basically it just disables type checking.

  • thoughtspile 3 days ago

    I'm afraid you're confusing something!

    The intersection of all types is never, because, say, number and string don't intersect. The union of all types is unknown.

    any doesn't show set-like properties and yields ternary logic in clauses like any extends T, which makes it a paradox.

hot_gril 3 days ago

Unrelated set theory thing: They say Typescript is a "superset" of Javascript, but plenty of valid JS code won't work in TS. For example, `let foo = 4; foo = "4";`

  • goodoldneon 3 days ago

    That isn't what "TS is a superset of JavaScript" means. That phrase means "TS syntax is a superset of JavaScript syntax". Your example is syntactically valid in TS -- it just fails a type checking

    • hot_gril 2 days ago

      I see what you're saying about syntax and type-checking being separate stages, but they're both compile-time (or do we call it transpile-time?) errors enforced by the language. Either way, I can't copy-paste working JS code into TS, which is what matters.

      There's probably another "asterisk" for this, but the other thing is how TS in Node by default won't allow `require` (vanilla NodeJS), only `import` (ESM).

      • goodoldneon 2 days ago

        Importing with `require` is CommonJS, not part of JavaScript’s spec. CommonJS is a Node-only thing so supporting it isn’t required in a JS superset

clord 3 days ago

Having set types like this and refining them smaller is something I wish Haskell would learn from Typescript, especially the automatic inference side. I wonder if it would help with linear types? Are there any proposals? I know there are type level naturals in the type system, but this is more like wanting to deconstruct existing types like Int or String into subset types.

e.g.,

foo :: Int -> 3::Int | 4::Int

foo 4 = 4

foo _ = 3

bar :: 3::Int | 4::Int -> Bool

bar 4 = True

bar 3 = False

-- (bar 12) is a compiler error, and no need to handle other patterns

baz :: 3::Int | 2::Int -> Bool

bar 3 = True

bar x = False -- Type of x is 2::Int

  • consilient 3 days ago

    > Having set types like this and refining them smaller is something I wish Haskell would learn from Typescript, especially the automatic inference side

    Haskell has far better type inference than Typescript in large part because it doesn't have subtyping.

    There are libraries for open records and sums (e.g. https://hackage.haskell.org/package/vinyl) but they're almost always the wrong choice.

    • Tainnor 2 days ago

      > Haskell has far better type inference than Typescript in large part because it doesn't have subtyping.

      That's a bit like saying "Go has a faster compiler because the language is so simple". It's true, but there's a genuine tradeoff here.

      Haskellers have convinced themselves that subtypes are not worth it - and they may be right in many cases - but subtyping is a quite natural way of modeling many concepts (for example: mathematical expressions). Some of that can be recovered by typeclasses, existential types and other machinery (or you could go as far as dependent types), but that seems all more cumbersome and hard to understand than the way subtyping works in languages that support it out of the box.

      • consilient 2 days ago

        That's my point. Haskell hasn't failed to "learn" from Typescript, it's picked a different point on the same frontier. There's no strictly improved structurally typed Haskell out there waiting to be written; there are fundamental obstacles in the way.

david-farr 3 days ago

Great article!

Not entirely related to the post, but what app did you use to create your diagrams? They look fantastic.