Hacker News new | ask | show | jobs
by esperent 1106 days ago
I'm reasonably proficient in Typescript although I wouldn't call myself an expert in type systems. But I'm not a beginner either.

However, I read though the readme and I have no idea what the usefulness of this is. Can anyone explain, in simple terms, some practical use cases for this?

3 comments

A situation where I needed this were typings for Cycle.js a couple years ago. At this time, you could configure it to use the observable library of your choice but it was hard to type. In particular the observable could be a `Stream from "xstream"` or `Observable from "rxjs"`

This lib has a method `select` to return an observable list of DOM elements for a given CSS selector. What should be its signature? If using `xstream`, it should be `select(selector: string): Stream<HTMLElement[]>`, and if using `rxjs` it should be `select(selector: string): Observable<HTMLElement[]>`. Let's also assume that it has a second method `windowHeight` returning an observable for the window height (`Stream<number>` or `Observable<number>`).

We can make the lib object generic over the observable implementation, but you need HKTs to type it properly. The reason is that the lib is generic over an already generic type.

Here is an example:

    // Without HKT (regular generic)
    // Problem: both `select` and `windowHeight` return the same type (we lose the HTMLElement[]/number information)
    interface Cycle<Obs> {
      select(selector: string): Obs;
      windowHeight(): Obs;
    }
    // The best we can do is type it as `Cycle<Stream<unknown>>` or `Cycle<rxjs.Observable<unknown>>`.
    
    // With HKTs, using syntax from this lib, you could do:
    interface Cycle<$HktObs> {
      select(selector: string): apply<$HktObs, HTMLElement[]>;
      windowHeight(): apply<$HktObs, number>;
    }
    // This allows to get the precise signatures we wanted (with the right observable impl, without `unknown`)
Higher (-order) kinded types describe functions on type constructors. Type constructors are "generic types", like `T[]` that generate a list from a single type `T` or like `Map<S, T>` that generates a new type from two types `S` and `T`. A higher kinded type constructor is one, where you could use any (well, with certain prerequisites) of these type constructors to generate a new type. Let's say you want to have a function `map` (like `Array.map`) that works not only with `T[]` but also with `Tree<T>`, `Maybe<T>` (`= T | undefined`), ... HKT give you the possibility to write the type of such type functions.
The thing is, it takes a bit of experience to appreciate why HKT are important, and typically you can only get this experience using Haskell.

There’s a couple of ways to think about it: it gives you a way to talk about List rather than List of T, it enables you to write partial types like partially-applied functions, or it makes it possible to define Monads.

But as I say, none of these things will sound immediately useful unless you have experience of using those concepts already.

I think there is a certain kind of programmer who enjoys the aesthetics of higher-kinded types, and after having made the investment to truly grok them, wants these HKTs to also be useful in practice.

I don’t think the benefit ever materializes and highly abstract code is just indulgence.

Much like the people who endlessly tinker with their IDE/emacs/desktop environment/shell in the name of productivity.

Honestly, they are useful in practice, as long as you’re using a language that supports them properly (I’m not convinced TS is that language). Even without, they constitute a pattern that is very simple to implement which is very powerful.

People believed (and some still do believe) the same thing about generics.

true
Other than Monads, HKT can be used to easily write type-level functional programs [1]. This can for example help writing type-level parsers for other lanugages.

A real world use-case could be parsing GraphQL raw string queries and automatically infer the returned types based on a common schema, without using special code-generators. For instance you can come up with some magic function `gql_parsed` like:

doc = gql_parsed`query GetUser { user { name }}`

where doc is inferred as something like Doc<Query<{GetUser:{user:{name:string}}}>>

[1] https://desislav.dev/blog/tsfp/

> The thing is, it takes a bit of experience to appreciate why HKT are important, and typically you can only get this experience using Haskell.

They're fairly common in Scala too, and I believe in OCaml through modules.

And C++ if you squint hard enough.
and F# with computations Seems common with functional programming, that it takes a mind-set change before seeing the need.