[HN Gopher] Functional Programming with TypeScript's Type System
       ___________________________________________________________________
        
       Functional Programming with TypeScript's Type System
        
       Author : evolveyourmind
       Score  : 81 points
       Date   : 2023-04-16 09:31 UTC (13 hours ago)
        
 (HTM) web link (desislav.dev)
 (TXT) w3m dump (desislav.dev)
        
       | hoelle wrote:
       | Gross. I love it.
        
       | ck45 wrote:
       | Related: "TypeScripting the technical interview"
       | https://news.ycombinator.com/item?id=35120084
        
       | coneonthefloor wrote:
       | What a monstrosity.
        
       | rdez6173 wrote:
       | Fun exploration and exceptionally impractical. Love it.
        
       | Waterluvian wrote:
       | While no actual Turing machine's tape is infinitely long, I found
       | issues in TypeScript with how finite generics are.
       | 
       | You have to define every possible count of generic arguments if
       | you want to preserve their types. And if you go above that count
       | your type system degrades. I think there's also a maximum of 7 or
       | so before it doesn't work. Beyond that and the generic type
       | widens.
       | 
       | For example, Lodash enumerating types for 2 to 7 generic items
       | per function:
       | https://github.com/DefinitelyTyped/DefinitelyTyped/blob/0452...
       | 
       | Admittedly I don't understand the problem space well. I've just
       | seen it happen to me and in others' code. It might not actually
       | be an issue, or is already fixed.
        
         | SirensOfTitan wrote:
         | This was fixed in Typescript 4.0, with the introduction of
         | variadic tuples:
         | https://www.typescriptlang.org/docs/handbook/release-notes/t...
        
           | Waterluvian wrote:
           | Oh awesome! Thanks for sharing.
        
       | nkrisc wrote:
       | As wonderfully absurd as this is, I learned more about
       | TypeScript's type system from this post than I have from its
       | documentation.
       | 
       |  _Entirely_ possible that PEBKAC, but I've found TypeScript's
       | documentation to be on the worse end of the programming language
       | documentation quality spectrum.
        
         | DangitBobby wrote:
         | It's not just you. To learn how to express more advanced types
         | (or learn whether they are even possible to express), I've had
         | to Google, read source code, or scan random medium articles and
         | blogs from tech companies. Rarely have I learned anything new
         | from the TS docs.
        
         | wooly_bully wrote:
         | It's good for reference but not for discovery. If I already
         | know the general concept (let's say Template Literal Types) I
         | can get good info on it, but if I start with a question like
         | 'Is there a way to make sure this string literal starts with
         | "id_"?' then I find it very hard to know.
         | 
         | Random but this is what I'm finding GPT-4 best at: translating
         | random questions into domain terminology + providing examples.
        
           | iudqnolq wrote:
           | maybe this is just an issue with me but I've not found any
           | way to search the docs. the only thing Google seems to index
           | is the release notes, and going backwards from the release
           | notes to guess the appropriate section of the docs that will
           | explain that concept is really annoying.
        
             | nkrisc wrote:
             | I can't even find basic type definitions for standard JS
             | functions. I have to type the function in my IDE and then
             | open the type definition from there.
             | 
             | So it works, I guess, but that's not really how I want to
             | work.
        
       | ekvintroj wrote:
       | Try to type a flatMap and then we talk.
        
         | klysm wrote:
         | Is this actually an issue?
        
         | [deleted]
        
         | anamexis wrote:
         | type ValueOrArray<T> = T | Array<ValueOrArray<T>>;         type
         | FlatMap<In, Out> = (array: Array<In>, fn: (el: In) =>
         | ValueOrArray<Out>) => Array<Out>;
        
           | ekvintroj wrote:
           | This does not work, that's the issue, try to run it.
        
             | anamexis wrote:
             | It seems to work for me, what issue are you seeing?
             | 
             | https://www.typescriptlang.org/play?target=6#code/C4TwDgpgB
             | A...
        
       | [deleted]
        
       | bottlepalm wrote:
       | I'm really interested in this for fun, but it's still way over my
       | head. And this is someone who has been using typescript daily for
       | years. Would love to read something more long form that builds up
       | to this.
        
       | firechickenbird wrote:
       | At this point I'm wondering if the TypeScript type system can be
       | used for dependant types that would allow formal verification of
       | the programs
        
         | remexre wrote:
         | That'd require it to be sound and programs to be total: an
         | infinite loop is a proof of anything, and type system
         | unsoundness leads to false proofs
        
       | jcparkyn wrote:
       | I've done my share of weird stuff with TS types before, but I'd
       | never seen the trick of using interfaces to make higher-order
       | functions. Neat.
        
       | [deleted]
        
       | foepys wrote:
       | I don't get TypeScript's type system.
       | 
       | It is obviously very powerful and can model very complex type
       | constraints.
       | 
       | But then you have stuff like this where it is not checking types
       | as I would expect:                 interface Foo { bar: string; }
       | const f = {bar: "foobar"} as Readonly<Foo>;       function
       | someFunc(): Foo {         return f; // No error or warning, even
       | with all strict flags enabled       }
        
         | rendall wrote:
         | Sorry, I don't get it. What do you expect to happen here?
        
           | PhilipRoman wrote:
           | Presumably the issue is that a Readonly<Foo> shouldn't be a
           | subtype of Foo
           | 
           | I should note that I haven't yet had the pleasure of using a
           | language that handles const-ness properly, as Readonly<T>
           | should be neither a subtype nor a supertype of T
        
             | rendall wrote:
             | As an aside, I'm on mobile and tried to visit the
             | TypeScript playground to play around with this, but
             | weirdly, the default code is an implementation of FizzBuzz!
             | There was not an obvious way to clear it to get a blank
             | editor. Even "select all" context menu was hijacked. So I
             | gave up. I'll have to file an issue.
        
               | eyelidlessness wrote:
               | What I do is delete all but a few characters after "code"
               | in the URL, then reload to get an empty playground. It's
               | annoying but it works.
        
         | LaGuardiaKid wrote:
         | Handling of readonly correctness is definitely something ts
         | doesn't handle well
        
         | globuous wrote:
         | Yeah, there are some weird stuff in typescript, for instance,
         | this typechecks                   class Animal {}
         | class Dog extends Animal {             woof() {}         }
         | class Cat extends Animal {             meow() {}         }
         | let append_animals = (animals: Animal[], animal: Animal) =>
         | animals.push(animal)              let dogs = [new Dog()]
         | append_animals(dogs, new Cat())              dogs.map(dog =>
         | dog.woof())
         | 
         | Which if you evaluate, you'll obviously get:
         | Uncaught TypeError: dog.woof is not a function
         | 
         | Whereas Mypy won't typecheck the equivalent Python code:
         | class Animal:             pass              class Dog(Animal):
         | pass                   def append_animals(animals:
         | List[Animal], animal: Animal) -> None:
         | animals.append(animal)                   dogs = [Dog()]
         | append_animals(dogs, Dog())
         | 
         | It'll throw with:                   $ mypy types.py
         | types.py:16: error: Argument 1 to "append_animals" has
         | incompatible type "List[Dog]"; expected "List[Animal]"
         | types.py:16: note: "List" is invariant -- see https://mypy.read
         | thedocs.io/en/stable/common_issues.html#variance
         | types.py:16: note: Consider using "Sequence" instead, which is
         | covariant
        
           | Shacklz wrote:
           | From my point of view, this is one of those cases where
           | structural typing just doesn't work all that well when used
           | for OOP.
           | 
           | Typescript does give you a solution to this problem, namely
           | that you use generics to constrain the parameters of your
           | method:                   let append_animals = <T extends
           | Animal>(animals: T[], animal: T) => animals.push(animal)
           | 
           | Your example now gives the expected error.
           | 
           | TypeScript does indeed have its quirks, but most of them do
           | not really matter for real-life purposes or can easily be
           | worked around like in the example above.
        
           | canadianfella wrote:
           | [dead]
        
           | brabel wrote:
           | The problem with your TS code is that it's using covariance
           | on a mutable generic type, which is unsafe and strict type
           | systems would've forbidden that.
           | 
           | To expand: TS treats `Dog[]` as a subtype of `Animal[]`
           | because `Dog` is a subtype of `Animal`... that work if you
           | only read values from the array... but trying to change the
           | array, you run into trouble. Some languages let you declare
           | covariance (reading ) and contravariance explicitly to
           | address this issue. To my limited knowledge of TS, that's not
           | possible in TS (as it tries to keep things simple and
           | compatible with JS, probably).
           | 
           | The answers in this[1] SO question explain these concepts
           | better than I could.
           | 
           | [1] https://stackoverflow.com/questions/27414991/contravarian
           | ce-...
        
             | DangitBobby wrote:
             | Why was the `dogs` array initialized as an `Animal[]` type
             | instead of `Dog[]` type which would forbid the addition of
             | a `Cat` type?
             | 
             | Why would you be able to map a call to `woof` over an
             | `Animal[]` when `Animal` doesn't implement `woof`? I don't
             | understand how the SO link answers these questions.
        
               | c-hendricks wrote:
               | > Why was the `dogs` array initialized as an `Animal[]`
               | type instead of `Dog[]`
               | 
               | That might be your confusion: it wasn't. Its type is
               | `Dog[]`.
               | 
               | > which would forbid the addition of a `Cat` type?
               | 
               | Why would that be forbidden? The problematic method is
               | `append_animals`, which only cares that both arguments
               | satisfy `Animal`, which both `Dog` and `Cat` do.
               | 
               | > Why would you be able to map a call to `woof` over an
               | `Animal[]` when `Animal` doesn't implement `woof`
               | 
               | Back to your root confusion, since for all intents and
               | purposes, `dogs.map` thinks it's an array of dogs, it
               | doesn't complain.
               | 
               | If `append_animals` was written like this, things would
               | be fine:                   let append_animals = <T
               | extends Animal>(animals: T[], animal: T) =>
               | animals.push(animal)
        
               | DangitBobby wrote:
               | I see what you're saying. Thanks for taking the time to
               | explain.
        
         | sakesun wrote:
         | This is an issue open for discussion since 2017
         | 
         | https://github.com/microsoft/TypeScript/issues/13347
        
         | rektide wrote:
         | Are there other examples of ways TS confounds you?
         | 
         | This feels like a "haha, gotcha!" moment. Like Gary Bernhardt's
         | _Wat_ talk, it 's one single example that looks extremely
         | silly... but has next to no actual impact on anyone using the
         | language regularly, is like the faintest little quirk.
         | 
         | Typescript seems reasonably acceptable. I struggle to think of
         | what I would ask for, what would be significantly massively
         | different in my life if there were a hypothetical much better
         | alternative to Typescript.
        
           | deathtrader666 wrote:
           | >> if there were a hypothetical much better alternative to
           | Typescript. Like ReScript ?
        
           | foepys wrote:
           | Don't get me wrong, I am very glad that TS exists and I am
           | using it at least weekly. But there are inconsistencies in
           | the type system that are very surprising to beginners because
           | TS is trying so hard to be good at edge cases.
        
         | _fat_santa wrote:
         | Having used TS in production with web and mobile (React Native)
         | apps, most of it is rather simple interfaces to make sure you
         | are passing data correctly. I'd say 99.9% of TS code I saw was
         | to make sure that you passed SuperComplexBusinessObject
         | correctly.
         | 
         | But like you, I've come across many instances where I got "hey
         | TS aren't you supposed throw an error here?"
        
       ___________________________________________________________________
       (page generated 2023-04-16 23:01 UTC)