solid-client-js icon indicating copy to clipboard operation
solid-client-js copied to clipboard

WIP: open discussion on typing

Open NSeydoux opened this issue 3 years ago • 7 comments

This commit includes two changes I want to bring up for discussion:

  • Defining the constants as raw literals makes the type system more assertive (here it goes from "string" to "http://www.w3.org/ns/solid/acp#allow" | "http://www.w3.org/ns/solid/acp#deny", which I find much more helpful
  • Making a function generic without changing its return type should not be necessary, because anything that matches ThingPersisted should be accepted in the proposed form, which means the generic does not add anything.

This is just a partial refactoring I want to use as a basis for conversation, it doesn't change any behaviour.

NSeydoux avatar Nov 23 '21 09:11 NSeydoux

This pull request is automatically built and testable in CodeSandbox.

To see build info of the built libraries, click here or the icon next to each commit SHA.

Latest deployment of this branch, based on commit 10881cfc24f996fa2f87d8a30048713e2614b8d8:

Sandbox Source
solid-client-sandbox Configuration

codesandbox-ci[bot] avatar Nov 23 '21 09:11 codesandbox-ci[bot]

Let's start with generic parameters.

What I see in my development environment is the following: Screenshot 2021-11-25 at 14 48 33

This illustrates that anything matching the structure of a type cannot be fed into non generic parameters.

Can you not reproduce this type Error?

Code snippet:

interface TypeX {
  a: string
}

function A(
  x: TypeX
): string {
  return x.a;
}

function B<T extends TypeX>(
  x: T
): string {
  return x.a;
}

A({ a: "bla", b: "bla" });

B({ a: "bla", b: "bla" });

matthieubosquet avatar Nov 25 '21 14:11 matthieubosquet

This looks good to me, also far easier to read than the concat + some editors will make the link actually clickable too.

I didn't actually see the change to getModes when I reviewed — the string changes look good to me though.

ThisIsMissEm avatar Nov 25 '21 15:11 ThisIsMissEm

Regarding the generic parameters, the behaviour you observe happens when you pass in a "plain" object, and not an explicitly typed variable. The following code snippet is valid too:

interface TypeX {
  a: string;
}

function A(x: TypeX): string {
  return x.a;
}

function B<T extends TypeX>(x: T): string {
  return x.a;
}

const myLiteral = { a: "bla", b: "bla" };
const myTypedVar: TypeX = myLiteral;

A(myTypedVar);
B({ a: "bla", b: "bla" });

Assigning const myTypedVar: TypeX = { a: "bla", b: "bla" } ; directly would fail though, for the same reason you're pointing out. Anything that is a supertype of TypeX is valid as an input to function A without generics, so I don't think we need them in this case. I think that the following also provides an interesting insight: https://effectivetypescript.com/2020/08/12/generics-golden-rule/

NSeydoux avatar Nov 25 '21 15:11 NSeydoux

const myLiteral = { a: "bla", b: "bla" };
const myTypedVar: TypeX = myLiteral;

This feels like it should fail and be an error..

ThisIsMissEm avatar Nov 25 '21 15:11 ThisIsMissEm

const myLiteral = { a: "bla", b: "bla" }; const myTypedVar: TypeX = myLiteral; This feels like it should fail and be an error..

Well the way I see it, myLiteral is of a supertype of TypeX, but TypeX would be a valid interface for myLiteral. I don't think TS types give any guarantee about things that should not be there, they only assert about what you can expect to find in my understanding. That's why it's valid to have const myVar: TypeA & TypeB being passed to a function that accepts TypeA, or to another function that accepts TypeB.

What would be an error though, is to do myTypedVar.b. Once it's been typed as some interface, there has been information loss, and the original underlying literal becomes irrelevant. It would then take a type guard to check whether myTypedVar does have a b property, and type it as TypeX & {b: string} or something.

NSeydoux avatar Nov 25 '21 16:11 NSeydoux

We have the same understanding Nicolas.

The syntax I'm proposing only really absolves you from casting. You can very well write:

  • A({ a: "bla", b: "bla" } as TypeX);
  • const x = { a: "bla", b: "bla" } as TypeX;
  • const x = { a: "bla", b: "bla" }; A(x);

Whether that syntax is more desirable than having a generic type input I'm not sure yet.


EDIT:

@NSeydoux very nice article by the way and I believe this rule of thumb in the TypeScript handbook about having all generics appearing at least twice is a good authoritative argument. https://microsoft.github.io/TypeScript-New-Handbook/everything/#type-parameters-should-appear-twice

I would say yes, let's factor all the ones that don't out.

matthieubosquet avatar Nov 25 '21 16:11 matthieubosquet

This is outdated now.

NSeydoux avatar Jan 04 '24 10:01 NSeydoux