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

Proposal: making Class and Module classes generic -- Class[I] and Module[I] #1542

Open
ParadoxV5 opened this issue Sep 24, 2023 · 10 comments
Open

Comments

@ParadoxV5
Copy link
Contributor

ParadoxV5 commented Sep 24, 2023

Currently, we model module/class themselves (as opposed to instances) as singleton(Klass), whereas the Module/Class classes can only represent the module/class of untyped.

I suggest combining those two by giving Module and Class a type variable representing their instances. For example, Class[String] (and Module[String] too to under covariance) is equivlaent to singleton(String).
For backward compatibility, singleton(I) can be a alternate (read: legacy) syntax for Module[I].

The immediate benefit is Class[I] RBS finally able to write def new: (…) -> I and def allocate: () -> I (rather than expecting type checkers to infer from singleton(I)).

Speaking of singleton(I)

| `singleton(` _class-name_ `)` (Class singleton type)

[I < Bound] … singleton(I) is invalid. (Update: this is #558) [I < singleton(Bound)] -> I works the limitation around, but what if I has other duties?

# Modules can’t inherit Classes, so I’m stuck with this design.
class MyClassWrapper[I < MyObject] < MyComponent
  def initialize: (singleton(I) klass) -> void #FIXME: Class[I] when
  def customized_new: (*args) -> I
end

This is also how Java does it (as early as their type args were born).

@soutaro
Copy link
Member

soutaro commented Sep 28, 2023

Class[String] cannot be equivalent to singleton(String), because singleton(String) has singleton methods.

I'm not sure if adding a generic parameter to Class makes sense, because it can only be used to allocate method. (RBS generates new for each classes with types.)

I'm curious if there are more use cases.

@ParadoxV5
Copy link
Contributor Author

ParadoxV5 commented Sep 28, 2023

Class[String] cannot be equivalent to singleton(String), because singleton(String) has singleton methods.

You are correct, the singleton(String) syntax enables special handling of singleton methods on the String class.
But also note that, anything can have singleton methods, not just classes (Class instances). And unfortunately, there is no RBS equivalent for:

class << SINGLETON = Superclass.new
  def my_singleton_method 

and the current workaround is:

SINGLETON: Superclass & _SINGLETON
interface _SINGLETON
  def my_singleton_method …

I'm not sure if adding a generic parameter to Class makes sense, because it can only be used to allocate method. (RBS generates new for each classes with types.)

I'm curious if there are more use cases.

class Class[I]
  # The attached objects of singleton classes is the only “instance” they can have,
  # vs. regular classes that have `#allocate`/`#new`
  def attached_object: () -> I
  # This is `[T < I] (Class[T]) -> void` with the ineffective `[T]` flattened.
  def inherited: (Class[I]) -> void
  # ditto
  def subclasses: () -> Array[Class[I]]
  # If RBS has explicit countervariance like Java does…
  def superclass: [I < T] () -> Class[T]
end

The top post also includes an abstraction of a user use case. Here’s my use case unabstracted.

@HoneyryderChuck
Copy link
Contributor

If I may jump in, but i'd consider reserving that syntax to solve either module mixins, or delegation, both of which aren't yet solved in rbs.

module A[B]
  # A mixin of B, meaning methods of A can call functions defined in B

# or

class A[B]
  # class a delegates methods to B, could also solve the "delegate" stdlib

@ParadoxV5
Copy link
Contributor Author

If I may jump in

of coarse you may

but i'd consider reserving that syntax to solve either module mixins, or delegation, both of which aren't yet solved in rbs.

module A[B]
  # A mixin of B, meaning methods of A can call functions defined in B

# or

class A[B]
  # class a delegates methods to B, could also solve the "delegate" stdlib

Let type variables go wild and do crazy things –

module A[B]
  include B

@ParadoxV5
Copy link
Contributor Author

@hanazuki
Copy link
Contributor

hanazuki commented Nov 29, 2023

Another use case in resolv: #1655

We could type Resolv::DNS::Resource::Generic.create(65280, 1).new('data').data as String if we could write the following type definitions:

class Resolv::DNS::Resource::Generic < Resolv::DNS::Resource
  def self.create: (Integer type_value, Integer class_value) -> Class[Resolv::DNS::Resource::Generic]
  def initialize: (String data) -> void
  def data: () -> String
end

@soutaro soutaro changed the title Proposal: Module[I] Proposal: making Class and Module classes generic -- Class[I] and Module[I] Nov 30, 2023
@soutaro
Copy link
Member

soutaro commented Nov 30, 2023

I prefer extending generics upper bounds for resolv:

class Resolv::DNS::Resource::Generic < Resolv::DNS::Resource
  def self.create: [T < singleton(Generic)] (Integer type_value, Integer class_value) -> T
end 

(singleton(Generic) cannot be written as an upperbound for now.)

@hanazuki
Copy link
Contributor

hanazuki commented Nov 30, 2023

class Resolv::DNS::Resource::Generic < Resolv::DNS::Resource
  def self.create: [T < singleton(Generic)] (Integer type_value, Integer class_value) -> T
end 

(singleton(Generic) cannot be written as an upperbound for now.)

Having played with RBS and Steep, I found this is already syntactically valid in RBS but is not yet supported by Steep for type checking. Is this right?

I thought singleton(C) was something related to C.new.singleton_class (that is a subtype of the type C), but it is actually C.singleton_class (the type of the value C). The following seem to be the most specific types that work with the current implementation of Steep.

class Resolv::DNS::Resource::Generic < Resolv::DNS::Resource
  def self.create: (Integer type_value, Integer class_value) -> singleton(Generic)
end

NB. singleton(C) < singleton(Generic) for any class C < Generic.

@soutaro
Copy link
Member

soutaro commented Dec 1, 2023

I found this is already syntactically valid in RBS

Right... I forgot it...

@ParadoxV5
Copy link
Contributor Author

ParadoxV5 commented Nov 19, 2024

Class[String] cannot be equivalent to singleton(String), because singleton(String) has singleton methods.

Before, String implicitly extend singleton(String); in this design, it would be extend Class[String].

And similarly, Class[String] implicitly extend Class[Class[String]], but I don’t think we could write singleton(singleton(String)) currently.

String.singleton_class.singleton_class #=> #<Class:#<Class:String>>

The implementation this proposal (or at least the shortcomings of singleton(…)) would

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Development

No branches or pull requests

4 participants