Analogs to Typescript utility types?

I’m learning rescript, and the forum has been really useful for unblocking.
On typescript i feel kind of used to utility types ( types that transform other types ) like Pick and Omit does Rescript has or allows something similar?

Ocaml (and rescript) type system is much less complex than typescript’s one and mostly nominal, so you don’t have those things, you could do it with a PPX but I don’t know any that’d do that even in ocaml. To be honest I’ve never really understood the need for such helpers.

2 Likes

I have found them very handy, along with indexed access types, i tend to use a top down approach, where i define big types and extract and transform sections, instead of creating small parts and them combining them in a bottom up fashion, is a matter of taste,

The Partial is very handy for ORMs or Databases where you send a subset of the fields to be updated

With typescript react you also can do

type myComponentProps = ComponentProps<typeof Component>

to extract the type of the props of any react component, then generate the props alone elsewhere in a typed way…

I am very used to them, so it’s very surprising for me to know your position about them, but i don’t think i’m right about this, just different taste and preferences.

It’s not really surprising in my opinion. A functional programming language will obviously be more focused towards compositional techniques, and an OOP/imperative language will be more focused towards inheritance-based techniques.

2 Likes

I’m not sure i get the point you are exposing, type level computing is not exclusive to inheritance or classes, indeed they shine more for transforming structural types.

1 Like

I hope this use cases are understandable

// On rescript you can define small types first to compose bigger ones
type carEngineKind = [#gas | #electric]

type carEngine = {
  kind: carEngineKind,
  horsepower: float,
}

type car = {
  id: string,
  brand: string,
  engine: carEngine,
}

type carWithoutId = {
  brand: string,
  engine: carEngine,
}
/*
 * car type is defined once, as the single source of truth
 * other types can derive from
 */
type car = {
    id: string,
    brand: string,
    engine: {
        kind: 'gas' | 'electric',
        horsepower: number
    },
  }

/*
 * Generate a new type from car without field 'id',
 * using Omit utility type
 */
type carWithoutId = Omit<car, 'id'>

// indexed access type allows to extract type of a field
type carEngine = car['engine']

type carEngineKind = car['engine']['kind']

There’s no type-of construct in the language, except for modules.
Type inference covers a lot of cases, but definitely this limits expressivity in data modeling. Eg: just like t but with one more field, is not expressible currently.

1 Like

Polymorphic variants, and object types (not record types) is where you find more flexibility, which you normally pay for in complexity.
It takes a little bit of experience, and of spending a couple of hours debugging a single confusing type error, to evaluate the trade offs.
Just staring at a couple of type definitions is not very informative of the pluses and minuses.

3 Likes

Yeah, thanks for the explanation, in Rescript defense, there are not many languages that support Type level programming, it just happens that its biggest competitor kind of supports it.

Call me crazy but I actually see this point in favour of ReScript. People get pulled into TypeScript with the promise of an easy transition from JavaScript and end up with slow builds and mind-bendingly complex types. ReScript deliberately stays away from that approach and provides a simpler one.

9 Likes

To solve this very specific example, you can build up (compose) your data instead of working backwards. I’ve found this kind of thing works well when adding 1 - 3 properties:

type carEngineKind = [#gas | #electric]

type carEngine = {
  kind: carEngineKind,
  horsepower: float,
}

// This is the base `car` type
type carProps = {
  brand: string,
  engine: carEngine,
}

type carId = string

// Wrapped via a variant for consistency,
// but you can always just use `carProps`
type car = Car(carProps)
type carWithId = CarWithId(carId, carProps)

// Or use multiple variants if needed, e.g. when deserialising 
type car = Car(carProps) | CarWithId(carId, carProps)

For me, this would logically be grouped into a Car module, too:

module Car = {
  type engineKind = [#gas | #electric]

  type engine = {
    kind: engineKind,
    horsepower: float,
  }

  type properties = {
    brand: string,
    engine,
  }

  type id = string

  type t =
    | Anonymous(properties)
    | Identified(id, properties)
}

This way, you’re still able to access the properties defined in the TS example, i.e. Car.engine, Car.engineKind, etc.

As @yawaramin says, I consider this to be a superior approach rather than a tradeoff.

4 Likes

Since ReScript only supports functional components, extracting the type for props of a component is essentially determining the type of a param to a function. This seems like a reasonable thing to do within a functional programming language. The use of OCaml’s type checker for ReScript is really a double edged sword. It’s battle tested and was likely easier than writing a type checker from scratch, but adding new concepts to it that are outside of its wheelhouse can be very difficult.

1 Like

adding new concepts to it that are outside of its wheelhouse can be very difficult.
why?
It’s the other way round. A sudden pressing need after 30 years of users who did not feel the need deserves a little bit of scrutiny.

1 Like

It’s the other way round. A sudden pressing need after 30 years of users who did not feel the need deserves a little bit of scrutiny.

People weren’t using OCaml to create React components before. There’s a bunch of other web APIs (not to mention TypeScript code) that ReScript code is interacting with and those APIs aren’t very OCaml-y so there’s impedance when trying to use those APIs in React.

1 Like

It’s solved by a decoding/encoding layer with migration to an ideomatic ReScript data structure. It’s true not only for REST APIs, but for browser ones too. It’s just usually better to use runtime free interop system instead.

This is all true, integrating natively with TS can be difficult in many cases, and the whole area has received less attention than it should have.
Just wanted to clarify that the difficulty is not extending Ocaml’s type system. That’s fairly easy to do.
What’s difficult, it extending it in a thoughtful way which preserves its good properties. Without starting to pile on feature after feature that might not interact well with each other and with the rest of the language.
So from a pragmatic standpoint, type system extensions need to pass a very high bar, and are subject to a lot of scrutiny.
All the more reason to think about those extensions to find the gems that will make real impact.

5 Likes