Hacker News new | ask | show | jobs
by kbr- 1362 days ago
Is there any place I could report issues or ask questions about the course other than Twitter?

If the author is reading this, the proposed solution to the `merge` challenge is:

  function merge<A, B>(a: A, b: B): A & B {
    return { ...a, ...b };
  }
That's the "obvious" solution, but it means that the following type-checks:

  const a: number = 1;
  const b: number = 2;
  const c: number = merge(a, b);
That's not good. It shouldn't type check because the following:

  const d: number = { ...a, ...b };
does not type check.

And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types).

Also, looking forward to further chapters.

2 comments

> And I don't know how to express the correct solution (i.e. where we actually assert that A and B are object types).

You can do this:

  function merge<
    A extends Record<string, unknown>,
    B extends Record<string, unknown>
  >(a: A, b: B): A & B {
    return { ...a, ...b }
  }

  const result = merge({ a: 1 }, { b: 2 })
This fails on:

  const result = merge({ a: 1 }, { a: "fdsfsd" })
The correct type is quite complex and depends on whether or not `exactOptionalPropertyTypes` is enabled.

EDIT: I think this is correct for when `exactOptionalPropertyTypes` is off.

  type OptionalKeys<T extends { [key in symbol | string | number]?: unknown }> = { [K in keyof T]: {} extends Pick<T, K> ? K : never }[keyof T]

  function merge<
    A extends { [K in symbol | string | number]?: unknown },
    B extends { [K in symbol | string | number]?: unknown },
  >(a: A, b: B): {
    [K in Exclude<keyof B, keyof A>]: B[K]
    } & {
      [K in Exclude<keyof A, keyof B>]: A[K]
    } & {
      [K in keyof A & keyof B]: K extends OptionalKeys<B> ? A[K] | Exclude<B[K], undefined> : B[K];
    }
That's for when `exactOptionPropertyTypes` is enabled. With it disabled, then you'd replace `Exclude<B[K], undefined>` with `B[K]`.

As to whether this is a good idea. Ah... it's not :P

Wow yeah it gets way too complex if you want to track the types of properties within the objects too! If that is the case, then I would just prefer to do this instead as it is much simpler:

  type Value = { a: string }
  const result = merge<Value, Value>({ a: 1 }, { a: "fdsfsd" })
Why Record and not object?

    Avoid the Object and {} types, as they mean 'any non-nullish value'.
    This is a point of confusion for many developers, who think it means 'any object type'.
https://github.com/typescript-eslint/typescript-eslint/blob/...
Linters for TypeScript recommend using `Record<string, any>` instead of `object`, since using the `object` type is misleading and can make it harder to use as intended.

See:

- https://typescript-eslint.io/rules/ban-types/

- https://github.com/typescript-eslint/typescript-eslint/issue...

- https://github.com/microsoft/TypeScript/issues/21732

- https://github.com/microsoft/TypeScript/pull/50666

Because you only want to merge two objects that have keys with string type. "object" is represented as Record<any, any>. That would mean, you can use any type as key. Here is an example:

  function merge<
    A extends object,
    B extends object
  >(a: A, b: B): A & B {
    return { ...a, ...b }
  }

  const result = merge(() => {}, () => {}) // should fail!
  const anotherResult = merge([1, 2], [3, 4]) // should fail!
Which is obviously not what you want.

This table here gives you a good overview of differences between object and Record<string, unknown>: https://www.reddit.com/r/typescript/comments/tq3m4f/the_diff...

Yeah, TypeScript gets funky around the boundary of "things that can only be objects", because JavaScript itself gets funky around "what is an object"

Technically TypeScript "object types" only describe properties of some value. And in JavaScript... arrays have properties, and primitives have properties. Arrays even have a prototype object and can be indexed with string keys. So... {} doesn't actually mean "any object", it means "any value"

At its boundaries, TypeScript has blind-spots that can't realistically be made totally sound. So the best way to think of it is as a 90% solution to type-safety (which is still very helpful!)