Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Difference types #1253

Closed
DimitarBogdanov opened this issue May 10, 2024 · 4 comments
Closed

Difference types #1253

DimitarBogdanov opened this issue May 10, 2024 · 4 comments
Labels
enhancement New feature or request

Comments

@DimitarBogdanov
Copy link

Problem

Consider this:

type X = { a: number }
type Y = { b: number }
type Z = { c: number }

type M = { a: number, b: number, c: number }

It's possible to create a type like M without writing the property names individually.

type XYZ = X & Y & Z

But it's not possible to create a type like M which excludes properties from another type.

type N = M / X

Use case

You have an Instance which has many objects in it. You want to use the Instance as a dictionary, where you can use e.g. part.Decal and use the child decal. This is certainly doable!

--!strict
local holder = workspace.Part

type Holder = typeof(holder) --> Part & <all children instances of the workspace part>

Now you want to make a function which returns the Holder type.

export type Holder = typeof(holder)
function getHolder() --[[ ... ]] end

But you don't want your function's consumer to have access to irrelevant methods such as GetChildren and Name on the Holder type. This is where a difference of sets comes in place (coloured area):
image

So we want to get everything from Holder and remove everything from Part on it. This could look something like this

type Holder = typeof(holder) / Part

And boom! Now we can return Holder and we won't see anything from Part.
P.S. I use / instead of - as the suggested syntax, because a minus looks awkward to me, but it's definitely not the only possible way!

@DimitarBogdanov DimitarBogdanov added the enhancement New feature or request label May 10, 2024
@goldenstein64
Copy link

goldenstein64 commented May 11, 2024

There is a draft RFC for negation types: luau-lang/rfcs#29. Typically, this would mean you could write your example as Holder & ~Part, but it places restrictions on structural types, which is your primary concern.

So the operator you're proposing here would cover a different use case, I just wanted to point out that if the negation type RFC was able to test negated structural types, it would cover your use case as well.

Also, it doesn't look like there are any type operations that could be mistaken for value operations, and I think that's because of casting; it happens inside expressions that execute at runtime.

-- what could this mean?
local a = b :: T / U

-- it could be one of:
local a = (b :: T) / U
local a = b :: (T / U)

Perhaps you could use the symbol set theory/Wikipedia uses, the backslash \ character.

@alexmccord
Copy link
Contributor

alexmccord commented May 11, 2024

This design looks a lot like omit<T, keyof<U>> so this is really asking for an omit type family.

And yeah, the design conflicts with local a = b :: T / U as above.

@DimitarBogdanov
Copy link
Author

Also, it doesn't look like there are any type operations that could be mistaken for value operations, and I think that's because of casting; it happens inside expressions that execute at runtime.

I did not consider the fact that types appear in expressions, you're correct. I agree with the backslash idea, actually like it a bit more.

This design looks a lot like omit<T, keyof> so this is really asking for an omit type family.

100% plausible, I'm not very well vested in TS.

@alexmccord
Copy link
Contributor

Going to close this. https://github.com/luau-lang/rfcs will be the right place to submit such a feature request.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
enhancement New feature or request
Development

No branches or pull requests

3 participants